C++'s 'virtual' modifier

C++'s virtual modifier defines polymorphic behavior of function members as well as special object composition with [multiple] inheritance. 'virtual' means 'indirection', either in method invocation or in access of derived objects.

Minimal class data definitions have been chosen for the purposes of illustrating the various features. In this paper, the short identifiers signify the following: T: type [class], t: variable of type T, f: function[-member], d: data-member. All data members have conveniently chosen to be of the size of a pointer (void*, int, in 'normal' mode [e.g. IPL32]).

The assembly samples are chosen to be x86, 32 bit. C++ object layout is compiler dependent; the samples reflect only one of the possible approaches.

virtual function call

Consider the following simple class, and a call on a reference of an instance of it [note that the actual T code is not needed] :
    class T {
    public :
        T();
        virtual int f();
    private :
        int d;
    };

    void x(T& t) {
        t.f();
    }
The function x is e.g. translated to the following assembly code :
    void __cdecl x(class T&):
        mov ecx,[esp+4]     ;t
        mov eax,[ecx]       ;vftable
        jmp [eax]           ;f
The call to f is done indirectly, via the type's virtual function table. &t begins with a pointer to T's virtual function table (virtual method table, vftable, vtbl); the first member of that vftable is a pointer to f [the first virtual method].

The object layout looks like this :

      object               vtbl
    +---------------+    +---------------+
    | vtbl        --+--->| f             |
    +---------------+    +---------------+
    | d             |
    +---------------+
Consider the above class definition without the virtual modifier on f.

x will e.g. translate to :

    void __cdecl x(class T&):
        mov ecx,[esp+4]                         ;t
        jmp (public: int __thiscall T::f(void)) ;f
t.f() is translated to a direct call. The reference variable t is not polymorphic in this case. Advantages of such non-polymorphic calls are the possibility to inline the called code [done by the optimizer], to possibly make code run faster.

In this case the object layout looks like [i.e. it's a C struct without any special C++ features] :

      object
    +---------------+
    | d             |
    +---------------+
A similar direct call is generated if the reference modifier & is left away in x's parameter definition :
    void __cdecl x(class T):
        lea ecx,[esp+4]                                 ;&t
        jmp (public: virtual int __thiscall T::f(void)) ;f
In this case the compiler knows &t's exact type (since it is no reference), and uses this knowledge for possible higher performance.

virtual multiple inheritance

Consider classes T1 and T2 to be similar defined as T above (all identifiers have a number suffix though). T3 virtually inherits from T1 and T2 (in that order), overwrites T1::f1 and T2::f2, and adds a new function-member f3. x3 is used to illustrate upcasting (widening), i.e. casting to base classes :
    class T3 : public virtual T1, public virtual T2 {
    public :
        T3();
        virtual int f1();
        virtual int f2();
        virtual int f3();
    private :
        int d3;
    };

    void x1(T1& t);
    void x2(T2& t);

    void x3(T3& t) {
        x1(t);
        x2(t);
    }
The x3 is e.g. translated to the following code :
    void __cdecl x3(class T3&):
        push esi
        mov esi,[esp+8]                     ;t
        mov eax,[esi+4]                     ;vbtable
        mov ecx,[eax+4]
        lea eax,[ecx+esi+4]                 ;(T1&)t [via vbtable]
        push eax
        call (void __cdecl x1(class T1&))
        add esp,4
        mov edx,[esi+4]                     ;vbtable
        mov eax,[edx+8]
        lea eax,[eax+esi+4]                 ;(T2&)t [via vbtable]
        push eax
        call (void __cdecl x2(class T2&))
        add esp,4
        pop esi
        ret
Casting to both T1& and T2& changes the this pointer (+ 16 bytes, + 28 bytes), which is done via a virtual base class table (vbtable, vbtbl).

The layout of a virtual multiple inheritance object may look like this :

               object               vtbl
             +---------------+    +---------------+
         t3: | vtbl        --+--->| f3            |      vbtbl
             +---------------+    +---------------+    +---------------+
             | vbtbl       --+------------------------>| t3 offset, -4 |
             +---------------+                         +---------------+
             | d3            |                         | t1 offset, 12 |
             +---------------+                         +---------------+
             | 0             |      vtbl1              | t2 offset, 24 |
             +---------------+    +---------------+    +---------------+
         t1: | vtbl1       --+--->| f1 thunk      |
             +---------------+    +---------------+
             | d1            |
             +---------------+
             | 0             |      vtbl2
             +---------------+    +---------------+
         t2: | vtbl2       --+--->| f2 thunk      |
             +---------------+    +---------------+
             | d2            |
             +---------------+
The object parts (specially t1 and t3) are not glued together; virtual derivations (T3) have additionally a virtual base class table; base classes are prepended by an additional index, which are used in the implemented thunks. The vbtable can be overwritten at derived object construction time. Virtual inherited classes constructors implement an additional parameter to suppress [normal] base class construction, which is required to realize common base class parts as single instances.

Object setup code is e.g. :

    public: __thiscall T3::T3(void):
        mov eax,[esp+4]
        push esi
        test eax,eax
        mov esi,ecx                                     ;this
        je 1f
        lea ecx,[esi+10h]                               ;(T1&)t
        mov [esi+4],(const T3::`vbtable')
        call (public: __thiscall T1::T1(void))
        lea ecx,[esi+1Ch]                               ;(T2&)t
        call (public: __thiscall T2::T2(void))
    1:                                                  ;base classes constructed
        mov eax,[esi+4]                                 ;vbtable
        mov [esi+8],0                                   ;d3
        mov [esi],(const T3::`vftable'{for `T3'})
        mov ecx,[eax+4]
        mov [ecx+esi+4],(const T3::`vftable'{for `T1'}) ;(T1&)t [via vbtable]
        mov edx,[esi+4]
        mov eax,[edx+8]
        mov [eax+esi+4],(const T3::`vftable'{for `T2'}) ;(T2&)t [via vbtable]
        mov ecx,[esi+4]
        mov eax,[ecx+4]
        lea edx,[eax-0Ch]
        mov [esi+eax],edx
        mov eax,[esi+4]
        mov eax,[eax+8]
        lea ecx,[eax-18h]
        mov [esi+eax],ecx
        mov eax,esi
        pop esi
        ret 4

    [thunk]:public: virtual int __thiscall T3::f1`vtordisp{-4,0}'(void):
        sub ecx,[ecx-4]
        jmp (public: virtual int __thiscall T3::f1(void))

    [thunk]:public: virtual int __thiscall T3::f2`vtordisp{-4,0}'(void):
        sub ecx,[ecx-4]
        jmp (public: virtual int __thiscall T3::f2(void))

    const T3::`vftable'{for `T1'}:
        ([thunk]:public: virtual int __thiscall T3::f1`vtordisp{-4,0}'(void))

    const T3::`vftable'{for `T2'}:
        ([thunk]:public: virtual int __thiscall T3::f2`vtordisp{-4,0}'(void))

    const T3::`vftable'{for `T3'}:
        (public: virtual int __thiscall T3::f3(void))

    const T3::`vbtable':
        -4  ;t3 offset
        12  ;t1 offset
        24  ;t2 offset
Interesting are the differences between the setup of this in T3::f1, T3::f2 and T3::f3: in f1, d3 is referenced by [ecx-8] (negative offset); in f2, d3 is referenced by [ecx-14h] (also a negative offset), in f3, d3 is referenced by [ecx+8].

Consider non-virtual inheritance, leaving away the virtual base class modifiers.

The x3 is e.g. translated to the following code :

    void __cdecl x3(class T3&):
        push esi
        mov esi,[esp+8]                     ;t
        push esi                            ;(T1&)t
        call (void __cdecl x1(class T1&))
        add esp,4
        lea eax,[esi+8]                     ;(T2&)t
        push eax
        call (void __cdecl x2(class T2&))
        add esp,4
        pop esi
        ret
Casting t to T2 changes the this pointer (+ 8 bytes), casting to T1 won't. This requires an additional virtual function table for the T2 part of the object. The first vftable includes the new function(s). Non-virtual multiple inheritance leads to simpler, more compact objects, which however lack some flexibility required with e.g. diamond-shaped class hierarchies.

The layout of a non-virtual multiple inheritance object may look like this :

               object               vtbl
             +---------------+    +---------------+
      t1,t3: | vtbl        --+--->| f1            |
             +---------------+    +---------------+
             | d1            |    | f3            |      vtbl2
             +---------------+    +---------------+    +---------------+
         t2: | vtbl2       --+------------------------>| f2            |
             +---------------+                         +---------------+
             | d2            |
             +---------------+
             | d3            |
             +---------------+
Object setup code is e.g. :
    public: __thiscall T3::T3(void):
        push esi
        push edi
        mov esi,ecx                                 ;this
        call (public: __thiscall T1::T1(void))      ;(T1&)t == t
        lea edi,[esi+8]                             ;(T2&)t
        mov ecx,edi
        call (public: __thiscall T2::T2(void))
        mov [edi],(const  T3::`vftable'{for `T2'})  ;vtbl2
        mov [esi+10h],0                             ;d3
        mov [esi],(const  T3::`vftable'{for `T1'})  ;vtbl
        mov eax,esi
        pop edi
        pop esi
        ret

    const T3::`vftable'{for `T1'}:
        (public: virtual int __thiscall T3::f1(void))
        (public: virtual int __thiscall T3::f3(void))

    const T3::`vftable'{for `T2'}:
        (public: virtual int __thiscall T3::f2(void))
Interesting are the differences between the setup of this in T3::f1 and T3::f2: in f1, d3 is referenced by [ecx+10h]; in f2, d3 is referenced by [ecx+8].


Eric Laroche, laroche@lrdev.com, Fri Mar 5 2004