I am considering using a class hierarchy with more than a single level of inheritance, where virtual member functions form a “chain”, for example:
struct Base
{ virtual void foo(); };
struct D1 : Base
{ virtual void foo(); };
struct D2 : D1
{ void foo(); };
However I would like to know before using similar code in practice if this would cause additional dynamic dispatch overhead when calling trough a base class pointer. Consider the following example:
D2 instance;
D1* d1ptr = &instance;
Base* baseptr = &instance;
//normal function call to "D2::foo()", no dynamic dispatch.
instance.foo();
//virtual function call to "D2::foo()", one dynamic dispatch.
d1ptr.foo();
//virtual function call to "D2::foo()", does this incur additional
//..overhead compared to "d1ptr.foo()"?
baseptr.foo();
In your example, the depth of the inheritance tree does not affect performance. The reason is simple, every instance has its pointer to some vtable (containing function pointers), and a virtual function call goes just thru that vtable.
In other words, your code works like the following C code:
struct vtable {
void (*fooptr) (struct D1*);
};
struct D1 {
const struct vtable*vptr;
};
struct D2 {
const struct vtable*vptr;
};
And e.g. baseptr->foo()
is “transformed” at compilation time to baseptr->vptr.fooptr(baseptr)
(and of course d1ptr->foo()
is “transformed” to d1ptr->vptr.fooptr(d1ptr)
etc…)
So the execution cost is the same: get the vtable, and call indirectly the function pointer inside. The cost remains the same even if you had a struct D4
subclass of struct D3
subclass of struct D2
subclass of struct D1
. You could have a struct D99
with 99 inheritances and the execution cost would remain the same.
On some processors, any indirect call may be slower than a direct call, e.g. because of branch predictor cost.
The data of the vtable themselves is built at compilation time (as constant static data).
Things become a bit more complex with virtual inheritance and/or multiple inheritance
BTW, some C object frameworks (e.g. GObject from Gtk, or several data structures inside the Linux kernel) also provide their vtable (or class data). Pointing to a data structure containing function pointers is quite common (even in C). Read also about closures and you’ll see a relation with “vtables” (both closures and objects are mixing code with data).
Some C++ compilers (e.g. GCC) may provide devirtualization as an optimization technique.
8