Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

choosing vptr in case of multiple inheritance

This is similar to many previous questions, but it asks something which I was not able to find answer.

#include <iostream>
using namespace std;

class Base1 {
    public:
        int b1_data;
        virtual void b1_fn() {cout << "I am b1\n";}
};
class Base2 {
    public:
        int b2_data;
        virtual void b2_fn() {cout << "I am b2\n";}
};
class Derived : public Base1, public Base2 {
    public:
        int d_data;
        void b1_fn() {cout << "I am b1 of d\n";}
        void b2_fn() {cout << "I am b2 of d\n";}
};

int main() {
    Derived *d = new Derived();
    Base1 *b1 = d;
    /*My observation mentioned below is implementation dependant, for learning,
    I assume, there is vtable for each class containing virtual function and in
    case of multiple inheritance, there are multiple vtables based on number of
    base classes(hence that many vptr in derived object mem layout)*/

    b1->b1_fn(); // invokes b1_fn of Derived because d points to 
                 // start of d's memory layout and hence finds vtpr to
                 // Derived vtable for Base1(this is understood)
    Base2 *b2 = d;
    b2->b2_fn(); // invokes b2_fn of Derived but how? I know that it "somehow" 
                 // gets the offset added to d to point to corresponding Base2 
                 // type layout(which has vptr pointing to Derived vtable for 
                 // Base2) present in d's memory layout. 
    return 0;
}

Specifically, how does b2 point to vptr for vtable of Derived for Base2 to get to b2_fn()? I've tried seeing memlayout dump from gcc but couldn't figure out much.

like image 974
Vishal Sahu Avatar asked Sep 13 '25 19:09

Vishal Sahu


1 Answers

The compiler in case of multiple inheritance, construct his vtables so that every subobject has an appropriate vtable. Of course this is implementation dependent (as the vtables themselves), but it would be organized like that:

  • A Base1 object has a vptr pointing to a vtable containing a unique pointer to Base1::b1_fn
  • A Base2 object has a vptr pointing to a vtable containing a unique pointer to Base2::b2_fn
  • A Derived object has a vptr pointing to a vtable that starts with the vtable layout corresponding to Base1, but extendeds it with the missing elements of Base2's vtable. With "Layout" I mean that the pointer for b1_fn() is at the same offset, but it might point to an overriding function. So, here, the table would contain Derived::b1_fn followed by Derived::b2_fn. This combined layout ensures that the Base1 subobject in Derived can share the vtable with its child.
  • But a Derived object is composed of 2 subobjects: so the Base1 subobject will be followed by a the Base2 subobject, which will have its own vtable, using the layout required for Base2, but again with Base2::b2_fn instead of the original one.

When casting the Derived pointer to a Base2 pointer, the compiler will make it point to the Base2 subobject with its vtable, by applying a fixed offset determined at compile time.

By the way if you would do a downcasting, the compiler would similarly use the fixed offset in the other direction, to find the start of the Derived. This is all pretty simple until you use virtual bases, in which the technique of the fixed offset can no longer work. Virtual base pointers must then be used as explained in this other SO answer

This Dr.Dobb's article is the best explanation for all these layouts with some good pictures.

like image 103
Christophe Avatar answered Sep 16 '25 11:09

Christophe