Well, after many good answers explaining, while looking up the exact position of the virtual base class in memory incurs a performance penalty, there is a follow up question: "Can this penalty be reduced?" Fortunately, there is a partial solution in form of the (not yet mentioned) final
keyword. In particular, calls from the class D
of the original example to the innermost base A
can usually be (almost) penalty-free, but in the general case only, if you final
ize D
.
For why this is necessary, let's look at a multilevel class hierarchy:
class Base {};
class ExtA : public virtual Base {};
class ExtB : public virtual Base {};
class ExtC : public virtual Base {};
class App1 : public Base {};
class App2 : public ExtA {};
class App3 : public ExtB, public ExtC {};
class SuperApp : public App2, public App3 {};
Because our App
lication classes can use various of the Ext
ension classes of our base class, none of those Ext
ension classes can know at compile time, where the Base
subobject will be located within the object, that they are called with. Rather, they have to consult the virtual table at runtime to find out. This is, because the various Ext
and App
classes can all be defined in different translation units.
But the same problem exists for the App
lication classes: Because App2
and App3
inherit a virtualized Base
via the Ext
ension class(es), they don't know at compile time, where that Base
subobject is located within their own objects. So each method of App2
or App3
has to consult the virtual table to find the location of the Base
subobject within their local objects. This is, because it is syntactically legal to later combine those App
classes further, as illustrated with the SuperApp
class in the above hierarchy.
Also note, that there is a further penalty, if the Base
class calls any virtual methods defined on the Ext
ension or App
lication level. That's because the virtual method will be called with this
pointing to a Base
object, but they have to adjust this to the beginning of their own object by again consulting the virtual table. If an Ext
ension or App
lication layer (virtual or non-virtual) method calls a virtual method defined on the Base
class, that penalty is incurred twice: First for finding the Base
subobject and then again for finding the real object relative from the Base
subobject.
However, if we know, that a SuperApp
combining several App
s won't be created, we can improve things a lot by declaring the App
classes final:
class App1 final : public Base {};
class App2 final : public ExtA {};
class App3 final : public ExtB, public ExtC {};
// class SuperApp : public App2, public App3 {}; // illegal now!
Because final
makes the layout immutable, methods of the App
lication classes don't need to go through a virtual table to find the Base
subobject anymore. They just add the known constant offet to the this
pointer, when calling any Base
method. And virtual callbacks at the App
lication layer can fixup the this
pointer easily again by subtracting a constant known offset (or even not fix it up at all and reference the various fields from the middle of the object instead). Methods of the Base
class also don't incur any penalty upon themselves, because inside that class, everything works normal. So in this three-level scenario with final
ized classes on the outmost level, only the execution of methods on the Ext
ensions level is slower, if they need to refer to fields or methods of the Base
class, or if they are virtually called from the Base
.
The backdraw of the final
keyword is, that it disallows all extensions. You cannot derive an App2a
from App2
anymore, even, if it doesn't require any of those Ext
ensions. And declaring a non-final
App2Base
and then final
App2a
and App2b
from it, would again incur penalties for all the methods in App2Base
, that refer to the original Base
. Unfortunately, the C++ Gods didn't give us a way to just unvirtualize a base class, but leave non-virtual extensions possible. They also didn't give us a way to declare a "master" Ext
ension class, whose layout stays fixed, even if other Ext
ensions with the same virtual Base
class are also added (in this case, all the non-master Ext
ensions would refer to the Base
subobject within the master Ext
ension).
The alternative to virtual inheritance like this is usually to add all the extension stuff to the Base
class. Depending on the application, that might require a lot of extra and often unused fields and/or a lot of extra virtual method calls and/or a lot of dynamic_cast
s, which all come with a performance penalty, too.
Also note, that in modern CPUs, the penalty after a mispredicted virtual function call is much higher than the penalty after a mispredicted this
pointer fixup. The first needs to throw away all results obtained on the wrong execution path and restart afresh on the right path. The later still needs to repeat all opcodes depending directly or indirectly on this
, but doesn't need to load and decode instructions again. BTW: The speculative execution with unknown pointer fixups is one of the reasons, why CPUs are vulnerable to Spectre/Meltdown type data leaks.
foo
could as well be virtual. – Stevenage