C++ Virtual Inheritance Memory Layout

2019-04-08 23:50发布

问题:

Virtual Inheritance Memory Layouts

I am trying to fully understand what is happening under the hood in the memory with virtual inheritance and vTables/vPtrs and what not.

I have two examples of code I have written and I understand exactly why they work however I just want to make sure I have the right idea in my mind, of the object memory layouts.

Here are the two examples in a picture, and I just wish to know if my idea of the memory layouts involved are correct.

Example 1:

class Top { public: int a;  };
class Left : public virtual Top {  public: int b; };
class Right : public virtual Top { public: int c; };
class Bottom : public Left, public Right { public:  int d; };

Example 2:

Same as above, but with:

class Right : public virtual Top {
public:
    int c;
    int a;  // <======= added this
};

回答1:

The C++ standard doesn't say much about the object layout. Virtual function tables (vtables) and virtual base pointers are not even part of the standard. So the question and the asnwers can only illustrate possible implementations.

A quick look on your drawing seems to show a correct layout.

You could be interested in these further references:

  • Multiple Inheritance Considered Useful a ddj article about layout in the case of multile inheritance and virtual inheritance.

  • Microsoft patent describing the use of vfptr (virtual function tables, aka vtables) and vbptr (virtual base poitners).

Edit: Does Bottom inherit Right::a or Left::a ?

In your test 2, Right and Left share the same common parent Top. So there is only one subobject Top within Bottom, and consequently, there is only one and the same Top::a.

Interestingly, you've introduced in your test 2 a member a in Right. This is an a which is different from the a inherited from Top. It's a distinct member, and just has the same name as another membemr "by coincidence". So if you access a via the Right, Right::a hides the Top::a (which is by the way also the Bottom::Top::a, the Right::Top::a, the Left::Top::a). In this case, bottom.a means the Right::a, not because of the object layout, but because of the name loockup (and hiding) rules. And this is not implementation dependent: it's standard and portable.

Here a variant for your test 2 to demonstrate the situation:

int main() {
    Bottom bottom; 
    bottom.a = 7; 
    cout << bottom.Top::a << endl << bottom.Left::Top::a << endl;
    cout << bottom.Right::Top::a << endl << bottom.Left::a << endl;
    cout << bottom.Right::a << endl <<endl;
    bottom.Right::a = 4; 
    bottom.Left::a = 3; 
    cout << bottom.Top::a << endl << bottom.Left::Top::a << endl;
    cout << bottom.Right::Top::a << endl << bottom.Left::a << endl;
    cout << bottom.Right::a << endl <<endl;

    cout << "And the addresses are: " << endl; 
    cout << " Bottom:       " << (void*)&bottom << endl; 
    cout << " Top:          " << (void*)static_cast<Top*>(&bottom) << endl;
    cout << " Left:         " << (void*)static_cast<Left*>(&bottom) << endl;
    cout << " Right:        " << (void*)static_cast<Right*>(&bottom) << endl;
    cout << " Top::a:       " << (void*)&bottom.Top::a << endl;
    cout << " Left::Top::a: " << (void*)&bottom.Left::Top::a << endl;
    cout << " Right::Top::a:" << (void*)&bottom.Right::Top::a << endl;
    cout << " Left::a:      " << (void*)&bottom.Left::a << endl;
    cout << " Rigth::a:     " << (void*)&bottom.Right::a << endl;
};