Multiple inheritance: class size for virtual pointers?

Based on the code:

class A{}; class B : public virtual A{}; class C : public virtual A{}; class D : public B,public C{}; int main(){ cout<<"sizeof(D)"<<sizeof(D); return 0; } 

Output: sizeof (D) 8

Each class contains its own virtual pointer just not for any of its base class. So, why is the size of class (D) equal to 8?

+6
source share
4 answers

It depends on the compiler implementation. My compiler is Visual Stdio C ++ 2005.

Code like this:

 int main(){ cout<<"sizeof(B):"<<sizeof(B) << endl; cout<<"sizeof(C):"<<sizeof(C) << endl; cout<<"sizeof(D):"<<sizeof(D) << endl; return 0; } 

He will deduce

 sizeof(B):4 sizeof(C):4 sizeof(D):8 

class B has only one virtual pointer. So sizeof(B)=4 . And class C as well.

But D has multiple inheritance from class B and class C Compilation does not combine two virtual tables. Thus, class D has two virtual pointer points to each virtual table.

If D only inherits one class and not virtual inheritance. It will combine their virtual table.

+3
source

It depends on the implementation of the compiler, so you must specify which compiler you are using. In any case, D comes from two classes, so it contains pointers to B and C vtables pointers to the base class (I don’t know, t know a good name for this).

To check this, you can declare a pointer to B and a pointer to C and point the address D to the pointer to the base class. Dump these values ​​and you will see that they are different!

EDIT
The test was performed with Visual C ++ 10.0, 32 bits.

 class Base { }; class Derived1 : public virtual Base { }; class Derived2 : public virtual Base { }; class Derived3 : public virtual Base { }; class ReallyDerived1 : public Derived1, public Derived2, public Derived3 { }; class ReallyDerived2 : public Derived1, public Derived2 { }; class ReallyDerived3 : public Derived2 { }; void _tmain(int argc, _TCHAR* argv[]) { std::cout << "Base: " << sizeof(Base) << std::endl; std::cout << "Derived1: " << sizeof(Derived1) << std::endl; std::cout << "ReallyDerived1: " << sizeof(ReallyDerived1) << std::endl; std::cout << "ReallyDerived2: " << sizeof(ReallyDerived2) << std::endl; std::cout << "ReallyDerived3: " << sizeof(ReallyDerived3) << std::endl; } 

Conclusion, guess not surprisingly:

  • Base: 1 byte (OK, this is a surprise, at least for me).
  • Derived1: 4 bytes
  • ReallyDerived1: 12 bytes (4 bytes per base class due to multiple inheritance)
  • ReallyDerived2: 8 bytes (as intended)
  • ReallyDerived3: 4 bytes (only one base class with virtual inheritance in the path, but this is not virtual).

Adding a virtual method to the database allows you to get 4 bytes for each class. Thus, it is likely that the extra bytes are not vtable pointers, but the base class pointers used in multiple inheritance do not change the removal of virtual inheritance (but if the virtual size does not change the addition of additional bases).

+2
source

First: without virtual functions, the likelihood that vptr generally in classes. The 8 bytes you see are an artifact about how virtual inheritance is implemented.

Often, you can use the same vptr for multiple classes in a hierarchy. For this, it is necessary that their offset to the final class must be the same, and for the list of records vtable to the base class, to be the initial sequence, the list of records vtable to the derived class.

Both conditions are satisfied in almost all implementations for single inheritance. No matter how deep the legacy is, there will usually be only one vptr shared by all classes.

In the case of multiple inheritance, there will always be at least one class for which these requirements are not met, since two base classes cannot have a common starting address, and if they do not have exactly the same virtual functions, only one vtable could be the initial the sequence is different.

Virtual inheritance adds another quirk, since the position of the virtual base relative to the class inherited from it will vary depending on the rest of the hierarchy. Most of the implementations that I have seen use a separate pointer for this, although this information should also be found in table v.

If we take your hierarchy by adding virtual functions so that we have some with vptr , we notice that B and D can still share vtable , but separate A and C required for both A and C This means that if your classes had virtual functions, you will need at least three vptr . (From this I conclude that your implementation uses separate pointers to a virtual base. When using B and D the same pointer and C with its own pointer. And, of course, A does not have a virtual base and does not need a pointer to itself.)

If you are trying to accurately analyze what is happening, I would suggest adding a new virtual function in each class and adding a pointer-size integral type that you start with a different known value for each class. (Use constructors to set the value.) Then create an instance of the class, take its address, then output the address for each base class. And then drop the class: known fixed values ​​will help you determine where the different elements are. Sort of:

 struct VB { int vb; VB() : vb( 1 ) {} virtual ~VB() {} virtual void fvb() {} }; struct Left : virtual VB { int left; Left() : left( 2 ) {} virtual ~Left() {} virtual void fvb() {} virtual void fleft() {} }; struct Right : virtual VB { int right; Right() : right( 3 ) {} virtual ~Right() {} virtual void fvb() {} virtual void fright() {} }; struct Derived : Left, Right { int derived; Derived() : derived( 5 ) {} virtual ~Derived() {} virtual void fvb() {} virtual void fleft() {} virtual void fright() {} virtual void fderived() {} }; 

You might want to add Derived2 , which comes from Derived and see what happens with relative addresses between, for example, Left and VB depending on whether the object is of type Derived or Derived2 .

+1
source

You make too many assumptions. It depends a lot on ABI, so you should study the documentation for your platform (I think you are working on a 32-bit platform).

Firstly, there are no virtual functions in your example, which means that none of the types contains a pointer to a virtual table. So where did these 2 pointers come from? (I assume you are on 32-bit architecture). Well, virtual inheritance is the answer. When you actually inherit, the relative location of the virtual base (A) relative to additional elements of the derived type (B, C) will change along the inheritance chain. In the case of an object B or C, the compiler can put types like [A, B '] and [A, C'] (where X 'are additional fields of X that are not present in A).

Now virtual inheritance means that in case D there will be only one subobject A, so the compiler can compose type D as [A, B ', C', D] or [A, C ', B', D] (or any other combination , A can be at the end of an object, etc., This is defined in ABI). So, what does this mean, this implies that the member functions from B and C cannot assume where the subobject A can be (in the case of non-virtual inheritance, the relative location is known), since the full type can actually be some other type down along the chain.

The solution to the problem is that both B and C usually contain an extra pointer to a pointer that is similar but not equivalent to a virtual pointer. Just as vptr is used to dynamically send to a function, this extra pointer is used to dynamically search for the database.

If you are interested in all these details, I recommend that you read Itanium ABI , which is widely used not only in Itanium, but also in other Intel 64 architectures (and a modified version in 32 architectures) by different compilers.

0
source

Source: https://habr.com/ru/post/910903/


All Articles