Virtual inheritance in C++

2019-05-14 01:09发布

问题:

I was reading the Wikipedia article on virtual inheritance. I followed the whole article but I could not really follow the last paragraph

This is implemented by providing Mammal and WingedAnimal with a vtable pointer (or "vpointer") since, e.g., the memory offset between the beginning of a Mammal and of its Animal part is unknown until runtime. Thus Bat becomes (vpointer,Mammal,vpointer,WingedAnimal,Bat,Animal). There are two vtable pointers, one per inheritance hierarchy that virtually inherits Animal. In this example, one for Mammal and one for WingedAnimal. The object size has therefore increased by two pointers, but now there is only one Animal and no ambiguity. All objects of type Bat will have the same vpointers, but each Bat object will contain its own unique Animal object. If another class inherits from Mammal, such as Squirrel, then the vpointer in the Mammal object in a Squirrel will be different from the vpointer in the Mammal object in a Bat, although they can still be essentially the same in the special case that the Squirrel part of the object has the same size as the Bat part, because then the distance from the Mammal to the Animal part is the same. The vtables are not really the same, but all essential information in them (the distance) is.

Can someone please shed some more light on this.

回答1:

Sometimes, you just really need to see some code / diagrams :) Note that there is no mention of this implementation detail in the Standard.

First of all, let's see how to implement methods in C++:

struct Base
{
  void foo();
};

This is similar to:

struct Base {};

void Base_foo(Base& b);

And in fact, when you look at a method call within a debugger, you'll often see the this argument as the first parameter. It is sometimes called an implicit parameter.

Now, on to the virtual table. In C and C++ it is possible to have pointers to function. A vtable is essentially a table of pointers to functions:

struct Base
{
  int a;
};

void Base_set(Base& b, int i) { b.a = i; }
int Base_get(Base const& b) { return b.a; }

struct BaseVTable
{
  typedef void (*setter_t)(Base&, int);
  typedef int (*getter_t)(Base const&);

  setter_t mSetter;
  getter_t mGetter;

  BaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gBaseVTable(&Base_set, &Base_get);

Now I can do something like:

void func()
{
  Base b;
  (*gBaseVTable.mSetter)(b, 3);
  std::cout << (*gBaseVTable.mGetter)(b) << std::endl; // print 3
}

Now, on to the inheritance. Let's create another structure

struct Derived: Base {}; // yeah, Base does not have a virtual destructor... shh

void Derived_set(Derived& d, int i) { d.a = i+1; }

struct DerivedBaseVTable
{
  typedef void (*setter_t)(Derived&,int);
  typedef BaseVTable::getter_t getter_t;

  setter_t mSetter;
  getter_t mGetter;

  DerivedBaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gDerivedBaseVTable(&Derived_set, &Base_get);

And the use:

void func()
{
  Derived d;
  (*gDerivedBaseVTable.mSetter)(d, 3);
  std::cout << (*gDerivedBaseVTable.mGetter)(d) << std::endl; // print 4
}

But how to automate this ?

  • you only need one instance of a vtable per class having at least one virtual function
  • each instance of the class will contain a pointer to the vtable as its first attribute (even though you can't really access it by yourself)

Now, what happens in case of multi-inheritance ? Well, inheritance is very much like composition in term of memory layout:

|                                     Derived                                   |
|                 BaseA                 |                 BaseB                 |
| vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |

There will thus be 2 virtual tables for MostDerived: one to change the methods from BaseA and one to change the methods from BaseB.

Pure virtual functions are generally represented as a null pointer (simply) in the corresponding field.

And finally, construction and destruction:

Construction

  • BaseA is constructed: first the vpointer is initialized, then the attributes, then the body of the constructor is executed
  • BaseB is constructed: vpointer, attributes, body
  • Derived is constructed: replace the vpointers (both), attributes, body

Destruction

  • Derived is destructed: body of the destructor, destroy attributes, put the base vpointers back
  • BaseB is destructed: body, attributes
  • BaseA is destructed: body, attributes

I think it's pretty comprehensive, I'd be glad if some C++ gurus around there could review this and check I haven't made any stupid mistake. Also, if something is missing, I'd be glad to add it.



回答2:

I cannot, really. This section tries to describe what should be done in a C++ implementation using virtual method tables to provide dynamic binding (in case of multiple inheritance).

If you're not doing a compiler, my advice is: Don't bother. Read your favorite C++ book on inheritance, virtual methods, mulitple inheritance and virtual inheritance.

Plus, usage of a vtable is not required by the C++ standard (IIRC), it's an implementation detail. So really, don't bother.



回答3:

As mkluwe suggested, vpointers are not really a part of the language. However, knowing about implementation techniques might be useful, especially in a low-level language like C++.

If you really want to learn this, I would recommend Inside the C++ Object Model, which explains this and a lot of other things in detail.