C++ diamond-shaped inherited object memory layout practical explanation and analysis

This article mainly explains the object model of diamond inheritance in the C++ object model, and discusses the inheritance of base class object variables and functions respectively.
What is diamond inheritance:
Diamond inheritance means that a base class (Base) derives two derived classes (Derived1, Derived2), and then these two derived classes (Derived1, Derived2) derive a final derived class, such as 1.1 As shown in the figure.

1. The non-virtual inheritance of diamond inheritance

1.1 UML structure diagram of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

Insert picture description here

1.2 Code definition of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

NonVirtualDerivedDiamondClass.cpp

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base(int x) : x(x) {
    
    }

protected:
	int x;

};

class Derived1 : public Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }

protected:
	int y1;
};

class Derived2 : public Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->x << endl;
	}

protected:
	int z;
};

1.3 The final derived class DDerived object model

Since the final derived class contains the object models of the base class Base, derived classes Derived1, and Derived2, only the object model of the final derived class DDerived can be analyzed.
VS2017 developer mode to view the C++ object model method can refer to this blog: C++ single-inheritance class object memory layout practical explanation and analysis

  • The layout of DDerived in memory
    Insert picture description here
    As can be seen from the above figure, the memory of the diamond inherited derived classes Derived1 and Derived2 respectively inherits and saves the member variable int x of the base class Base. When we call the member variable x on the final derived class DDerived, there will be ambiguity, DDerived does not know which object x is called. At this time, the compilation will report an error, and the member variable x is called ambiguous, as shown in the figure below.
    Insert picture description here
    If we want to call the inherited x in the final derived class Derived, then we must display the scope ("::") qualifier of the designated call to indicate which base class inherited x is called, that is, this->Derived2: :x, as shown in the following code:
class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->Derived2::x << endl; // 显示指定作用域Derived2::x,调用Derived2的成员变量x
	}

protected:
	int z;
};

It can be seen from the memory layout of Dderived that the class objects of the derived classes Derived1 and Derived2 each store a member variable int x inherited from the base class Base; this will not only cause ambiguity in obtaining the variable x of the final derived class Dderived, At the same time it will cause a waste of memory. So, is there a way to solve these problems? The answer is yes, and that is to use virtual inheritance.

2. Virtual inheritance of diamond inheritance

2.1 UML structure diagram of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

Insert picture description here
As can be seen from the above figure, only the derived class Derived1 and derived class Derived2 use virtual inheritance when inheriting from the class Base, while the final derived class DDerived inherits Derived1 and Derived2 using ordinary inheritance. which is

Derived1 : public virtual Base {
    
     ... };
Derived2 : public virtual Base {
    
     ... };
DDerived : public Derived1, public Derived2 {
    
     ... };

2.2 Code definition of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

VirtualDerivedDiamondClass.cpp

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base() = default;
	Base(int x) : x(x) {
    
    }

protected:
	int x;

};

class Derived1 : public virtual Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }

protected:
	int y1;
};

class Derived2 : public virtual Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->Derived2::x << endl;
	}

protected:
	int z;
};

2.3 The final derived class DDerived object model

Since the final derived class contains the object models of the base class Base, derived classes Derived1, and Derived2, only the object model of the final derived class DDerived can be analyzed.
VS2017 developer mode to view the C++ object model method can refer to this blog: C++ single-inheritance class object memory layout practical explanation and analysis

  • DDerived layout in memory Insert picture description here
    is apparent from the figure, the derived class DDerived final object model, derived class and derived classes Derived1 Derived2 have no member variables of a base class Base int x; memory, but a plurality of virtual pointer. The virtual pointers respectively point to their respective virtual function tables. The offset address of the variable x is stored in the virtual function table. By the offset address derived class Derived1 and derived class Derived2 can get the variable x. At this point, the derived class can directly use the this pointer to call the variable x without ambiguity, as shown in the following figure.
    Insert picture description here
    Therefore, virtual inheritance is mainly to inherit the offset address of the base class member variable. The offset address is stored in the virtual function table pointed to by the virtual pointer, and the order of arrangement is in accordance with the declaration order of the variables. As shown in the figure below:
    Insert picture description here
    the virtual inherited class has no virtual function, so if there is a virtual function in the base class, then what is the class object model of the diamond inherited final derived class after virtual inheritance? Next, continue to analyze and discuss.

Third, the virtual inheritance of diamond inheritance with virtual functions in the base class

3.1 UML structure diagram of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

Insert picture description here
As can be seen from the above figure, the base class Base and the derived classes Derived1 and Derived2 have virtual destructors and a virtual function vfun1();, indicating that this is a class with virtual functions in inheritance, that is, a class of non-POD type, and a memory object You cannot copy memcpy(...) byte by byte.

3.2 Code definition of class Base, derived class Derived1, derived class Derived2, and final derived class DDerived

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base() = default;
	virtual ~Base() {
    
    }
	
	Base(int x) : x(x) {
    
    }

protected:
	int x;

private:
	virtual void vfun1() = 0;
};

class Derived1 : public virtual Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }
	virtual ~Derived1() {
    
    }
	virtual void vfun1() override
	{
    
    
		cout << "virtual Derived1::vfun1()" << endl;
	}

protected:
	int y1;
};

class Derived2 : public virtual Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }
	virtual ~Derived2() {
    
    }
	virtual void vfun1() override
	{
    
    
		cout << "virtual Derived2::vfun1()" << endl;
	}

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }

	virtual void vfun1() override
	{
    
    
		cout << "virtual DDerived::vfun1()" << endl;
	}

	void callX()
	{
    
    
		cout << this->x << endl;
	}

protected:
	int z;
};

3.3 The final derived class DDerived object model

Insert picture description here
Figure 3-1 inheritance of imaginary rhombic virtual functions inherited FIG.
Insert picture description here
FIG. 3-2 is not a function of the succession of virtual rhombic FIG virtual inheritance
can be seen from the comparison of FIGS. 3-1 and FIGS. 3-2, with a succession of imaginary rhombic virtual functions The inherited final derived class DDerived object model is basically the same as the virtual inherited final derived class DDerived without virtual functions. There is only one difference, that is, the base class Base has an additional virtual pointer, which points to the virtual function of DDerived itself. table. This virtual function table is the same as the single-inherited virtual function table, which stores the virtual function of Dderived itself or the virtual function inherited from it. The definition rule of the virtual function table is to first copy the content of the base class virtual function table to Dderived's own virtual function table, and then use Dderived's own virtual function to overwrite the virtual function of the same name in the virtual function table.
In the same way, when there are static member functions, static member variables, and ordinary member functions, Dderived's class memory model is also not affected. The specific code blogger will not post it, leaving a small assignment for readers to verify by themselves.

Four, summary

  • After the diamond-shaped virtual inheritance, the member variables of the base class have only one memory, and the same member variables will not be copied in the derived class to occupy the memory;
  • After virtual inheritance, the derived class will not copy the base class member variables, but generate a virtual function table with a virtual pointer pointing to itself. The virtual function table stores the offset address of the base class member variable;
  • The class in virtual inheritance has virtual functions. There is only one difference from virtual inheritance without virtual functions, that is, the virtual pointer of the current class is generated, the virtual pointer points to the virtual function table of the final derived class, and the virtual function table stores the final derived class. All virtual functions after replacement or inherited virtual function addresses;
  • Only non-static members occupy the memory of the object model;
  • The static variables and static functions of the class object do not occupy the memory of the object model and are stored in the static storage area;
  • Ordinary member functions of the class object do not occupy the memory of the object model, and are stored in the ordinary data area

Five, reference content

The diamond-shaped inheritance problem of
C++ C++ object model and layout (the memory layout of three classic class objects)
The basic concept of diamond-shaped inheritance in
C++ and the memory occupation problem C++ inheritance (multiple inheritance + multiple inheritance + virtual inheritance + virtual destructor + redefinition )
"Deep Exploration of C++ Object Model" Hou Jie page: 83-134

Guess you like

Origin blog.csdn.net/naibozhuan3744/article/details/114192980