c++vtablevirtual-inheritancememory-layoutvptr

Why does virtual inheritance need a vtable even if no virtual functions are involved?


I read this question: C++ Virtual class inheritance object size issue, and was wondering why virtual inheritance results in an additional vtable pointer in the class.

I found an article here: https://en.wikipedia.org/wiki/Virtual_inheritance

which tells us:

However this offset can in the general case only be known at runtime,...

I don't get what is runtime-related here. The complete class inheritance hierarchy is already known at compile time. I understand virtual functions and the use of a base pointer, but there is no such thing with virtual inheritance.

Can someone explain why some compilers (Clang/GCC) implement virtual inheritance with a vtable and how this is used during runtime?

BTW, I also saw this question: vtable in case of virtual inheritance, but it only points to answers related to virtual functions, which is not my question.


Solution

  • The complete class inheritance hierarchy is already known in compile time.

    True enough; so if the compiler knows the type of a most derived object, then it knows the offset of every subobject within that object. For such a purpose, a vtable is not needed.

    For example, if B and C both virtually derive from A, and D derives from both B and C, then in the following code:

    D d;
    A* a = &d;
    

    the conversion from D* to A* is, at most, adding a static offset to the address.

    However, now consider this situation:

    A* f(B* b) { return b; }
    A* g(C* c) { return c; }
    

    Here, f must be able to accept a pointer to any B object, including a B object that may be a subobject of a D object or of some other most derived class object. When compiling f, the compiler doesn't know the full set of derived classes of B.

    If the B object is a most derived object, then the A subobject will be located at a certain offset. But what if the B object is part of a D object? The D object only contains one A object and it can't be located at its usual offsets from both the B and C subobjects. So the compiler has to pick a location for the A subobject of D, and then it has to provide a mechanism so that some code with a B* or C* can find out where the A subobject is. This depends solely on the inheritance hierarchy of the most derived type---so a vptr/vtable is an appropriate mechanism.