Is there any penalty/cost of virtual inheritance in C++, when calling non-virtual base method?
There may be, yes, if you call the member function via a pointer or reference and the compiler can't determine with absolute certainty what type of object that pointer or reference points or refers to. For example, consider:
void f(B* p) { p->foo(); }
void g()
{
D bar;
f(&bar);
}
Assuming the call to f
is not inlined, the compiler needs to generate code to find the location of the A
virtual base class subobject in order to call foo
. Usually this lookup involves checking the vptr/vtable.
If the compiler knows the type of the object on which you are calling the function, though (as is the case in your example), there should be no overhead because the function call can be dispatched statically (at compile time). In your example, the dynamic type of bar
is known to be D
(it can't be anything else), so the offset of the virtual base class subobject A
can be computed at compile time.
Yes, virtual inheritance has a run-time performance overhead. This is because the compiler, for any pointer/reference to object, cannot find it's sub-objects at compile-time. In constrast, for single inheritance, each sub-object is located at a static offset of the original object. Consider:
class A { ... };
class B : public A { ... }
The memory layout of B looks a little like this:
| B's stuff | A's stuff |
In this case, the compiler knows where A is. However, now consider the case of MVI.
class A { ... };
class B : public virtual A { ... };
class C : public virtual A { ... };
class D : public C, public B { ... };
B's memory layout:
| B's stuff | A's stuff |
C's memory layout:
| C's stuff | A's stuff |
But wait! When D is instantiated, it doesn't look like that.
| D's stuff | B's stuff | C's stuff | A's stuff |
Now, if you have a B*, if it really points to a B, then A is right next to the B- but if it points to a D, then in order to obtain A* you really need to skip over the C sub-object, and since any given B*
could point to a B or a D dynamically at run-time, then you will need to alter the pointer dynamically. This, at the minimum, means that you will have to produce code to find that value by some means, as opposed to having the value baked-in at compile-time, which is what occurs for single inheritance.
At least in a typical implementation, virtual inheritance carries a (small!) penalty for (at least some) access to data members. In particular, you normally end up with an extra level of indirection to access the data members of the object from which you've derived virtually. This comes about because (at least in the normal case) two or more separate derived classes have not just the same base class, but the same base class object. To accomplish this, both of the derived classes have pointers to the same offset into the most derived object, and access those data members via that pointer.
Although it's technically not due to virtual inheritance, it's probably worth noting that there's a separate (again, small) penalty for multiple inheritance in general. In a typical implementation of single inheritance, you have a vtable pointer at some fixed offset in the object (quite often the very beginning). In the case of multiple inheritance, you obviously can't have two vtable pointers at the same offset, so you end up with a number of vtable pointers, each at a separate offset in the object.
IOW, the vtable pointer with single inheritance is normally just static_cast<vtable_ptr_t>(object_address)
, but with multiple inheritance you get static_cast<vtable_ptr_t>(object_address+offset)
.
Technically, the two are entirely separate -- but of course nearly the only use for virtual inheritance is in conjunction with multiple inheritance, so it's semi-relevant anyway.