I assume the following definitions (remember that a
struct is defined as a class with default public:
access):
struct Base {
Base() : x(4) {}
virtual int get_value() { return x; }
int x;
};
struct Derived : public Base {
int get_value() { return 2 * x; }
};
...
Base b;
Derived d;
Base *dp = &d; // pointer (upcasted)
Base &dr = d; // reference (upcasted)
Each class with virtual member functions has a static virtual function table
(Vtable) containing pointers to each virtual member function for
that class.
Each object has a Vtable pointer (vptr), which points to the Vtable of its class after initialization
To inspect the Vtables with g++, you can use:
g++ -fdump-class-hierarchy program.cpp
This creates a new file program.cpp.class:
Vtable for Base
Base::_ZTV4Base: 3u entries
0 0u // ? (some sort of offset)
4 (int (*)(...))(&_ZTI4Base) // Base typeinfo
8 Base::get_value
Class Base
size=8 align=4
base size=8 base align=4
Base (0xb7d20800) 0
vptr=((&Base::_ZTV4Base) + 8u) // vptr = address of first function
// in Vtable
Vtable for Derived
Derived::_ZTV7Derived: 3u entries
0 0u // ? (some sort of offset)
4 (int (*)(...))(&_ZTI7Derived) // Derived typeinfo
8 Derived::get_value
Class Derived
size=8 align=4
base size=8 base align=4
Derived (0xb7d20e00) 0
vptr=((&Derived::_ZTV7Derived) + 8u) // vptr = address of first
Base (0xb7d20e40) 0 // function in Vtable
primary-for Derived (0xb7d20e00)
This is the Vtable generated for Base by g++, in assembler code:
.weak _ZTV4Base
.section .gnu.linkonce.r._ZTV4Base,"a",@progbits
.align 8
.type _ZTV4Base, @object
.size _ZTV4Base, 12
_ZTV4Base: ; class Base Vtable
.long 0 ; ? (some sort of offset)
.long _ZTI4Base ; Base typeinfo (defined elsewhere)
.long _ZN4Base9get_valueEv ; Base::get_value
And the Vtable generated for Derived:
.weak _ZTV7Derived
.section .gnu.linkonce.r._ZTV7Derived,"a",@progbits
.align 8
.type _ZTV7Derived, @object
.size _ZTV7Derived, 12
_ZTV7Derived: ; class Derived VTable
.long 0 ; ? (some sort of offset)
.long _ZTI7Derived ; Derived typeinfo (defined elsewhere)
.long _ZN7Derived9get_valueEv ; Derived::get_value
The Derived constructor:
.section .gnu.linkonce.t._ZN7DerivedC1Ev,"ax",@progbits
.align 2
.weak _ZN7DerivedC1Ev
.type _ZN7DerivedC1Ev, @function
_ZN7DerivedC1Ev:
pushl %ebp ; save caller base pointer
movl %esp, %ebp ; define callee base pointer
; push 'this' to stack:
subl $4, %esp ; allocate 1 word on stack for 'this'
movl 8(%ebp), %eax ; copy 'this' to eax
movl %eax, (%esp) ; ... and from there to top of stack
call _ZN4BaseC2Ev ; call Base::Base (non-static member
; functions always use 'this' as an
; implicit first argument)
movl 8(%ebp), %eax ; copy 'this' to eax
movl $_ZTV7Derived+8, (%eax) ; copy Derived Vtable to 'this'
; i.e. store Vtable in first attribute
; of 'this' (vptr). Note that the
; vptr points to the first function,
; Derived::get_value
leave ; restore caller stack frame
ret ; return to caller
.size _ZN7DerivedC1Ev, .-_ZN7DerivedC1Ev
Non-virtual method call d.get_value():
leal -16(%ebp), %eax ; copy pointer to 'd' in eax
movl %eax, (%esp) ; ... and from there to top of stack
call _ZN7Derived9get_valueEv ; call Derived::get_value() with
; pointer to 'd' as implicit first
; argument
Virtual method call dr.get_value():
movl -20(%ebp), %eax ; store pointer to 'd' in eax
movl (%eax), %edx ; copy vptr (first element of 'd') to
; edx
movl -20(%ebp), %eax ; store pointer to 'd' in eax (again)
movl %eax, (%esp) ; ... and from there to top of stack
movl (%edx), %eax ; copy first element in Vtable
; (Derived::get_value) to eax
call *%eax ; call Derived::get_value
Obviously, there is a small performance issue with virtual methods. If you do high-performance applications, note that this example performs perfectly in the time domain (although not in the space domain):
struct Thing {
virtual ~Thing() {}
virtual int method() = 0;
};
struct Thing2 : public Thing {
int method() { return 10; } // note: not virtual
};
int main() {
Thing2 thing;
return thing.method();
}
The 'main' code, when optimized, resolves to:
main:
pushl %ebp
movl $10, %eax
movl %esp, %ebp
popl %ebp
ret
Which is equivalent to:
int main() {
return 10;
}
If we did:
int main() {
Thing2 thing2;
Thing & thing = thing2;
return thing.method();
}
We would have the usual virtual method overhead.