3

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.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Vishal Sahu
  • 650
  • 12
  • 23
  • 2
    So your question is: "How does GCC implement virtual functions with multiple inheritance"? – Christian Hackl Mar 18 '16 at 18:13
  • Kind of yes, but to narrow it down, I want to know how does it perform binding when there are multiple base classes and all are not mapped in derived object layout at same offset. Basically the source of this "offset" is I am looking for. – Vishal Sahu Mar 18 '16 at 18:17
  • If you are interested in those kinds of information: https://www.amazon.com/dp/0201834545 – Sebastian Jun 02 '22 at 18:58

2 Answers2

3

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.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • "_Virtual base pointers must then be used_" this is just one implementation choice, not a need – curiousguy Mar 23 '16 at 00:09
  • @curiousguy yes of course ! virtual base pointers and vptr are both implementation mechanism used to implement the C++ standard, and do not belong themselves to the standard. So any alternative achieving the same effects could be used. For the sake of curiosity, have you any specific one in mind ? – Christophe Mar 23 '16 at 00:14
  • There is no viable alternative to vptr, but virtual base pointers have an obvious alternative: no virtual base pointers. The offset of the virtual base is in the vtable. GCC rules. – curiousguy Mar 23 '16 at 01:51
  • @Christophe The article you linked is very interesting. But it was incomplete in the end :( Btw, in the last section - in context of virtual inheritance, author says `Compilers are free to do something else, and in this case, they probably will do something else.` If you know that `something else` can you explain.. Or point me to an article about that! – Sourav Kannantha B May 15 '21 at 20:21
  • @Christophe I got the link to the second part of that article. The _next_ button at the end of page was not working when I tried. – Sourav Kannantha B May 15 '21 at 20:28
  • @SouravKannanthaB There are not so many ways of doing it, and despite what the article says, this is the foundation of a lot of compilers. I think I’ve seen dome patents on the subject for slight alternatives. But in the end, it doesn’t matter: vptr and vtables ate not even mentioned in the standard, and the standard is all what matters for safe code. – Christophe May 15 '21 at 20:36
0
#include <iostream>

using namespace std;


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

int main() {


Base1 b1;
Base2 b2;
Derived d;

cout<<"size of base1 object"<<sizeof(b1)<<endl;
cout<<"size of base2 object"<<sizeof(b2)<<endl;
cout<<"size of derived object"<<sizeof(d)<<endl;
return 0;

}

Output of above program is: 8 8 16

So you can see that derived object has inherited two vptr from base1 and base2

both vptr in derived class points to vtable of derived class. since you have overriden the base methods in derived class so vptr will point to the derived class methods.

In case you had not overriden methods in derived class than vtable would be having address of base class methods and vptr would be pointing to those

Thats why b2->b2_fn(); calls derived method

user265906
  • 120
  • 1
  • 8