C++ Advanced - Inheritance (below) Diamond (Virtual) Inheritance Analysis && Virtual Inheritance Storage Object Model

 

Table of contents

0. Preface

1. Under ordinary multiple inheritance, base class and derived class copy and transform the underlying details (slicing)

2. Complex diamond inheritance under multiple inheritance

3. Key points of rhombus virtual inheritance (virtual base class)

3.1 Diamond non-virtual inheritance object storage model

3.2 Diamond virtual inheritance object storage model

3.3 Virtual Inheritance Object Storage Model

3.4 Multi-object inheritance relationship analysis of its virtual base class & virtual inheritance position

5. Summary and reflection on inheritance


0. Preface

This article is mainly a continuation of the previous article, from a deeper understanding of common inheritance slice cutting and virtual inheritance slice cutting , from the bottom virtual memory analysis, and analysis of some problems caused by C++ multiple inheritance, and C++ to solve the problems caused by multiple inheritance. method, and observe its gradual implementation and principles from the underlying memory, and finally experience multiple inheritance at a deeper level! And inherit and combine two concepts from software engineering analysis ! ! !

1. Under ordinary multiple inheritance, base class and derived class copy and transform the underlying details (slicing)

Objects of derived classes can be assigned to objects of base classes/pointers of base classes/references of base classes . There is a figurative term here called slicing or cutting. It means to cut the parent class part of the derived class and assign it to the past.

Then how does the compiler slice when processing under common inheritance? Based on the above question, see the memory analysis as follows:

It can be seen that during the compilation phase, when the object is instantiated, the instantiated object only stores member variables, and the member functions will be stored in the public code segment according to their template parameters and class fields, so that they can be called!

When the object is instantiated, it can be concluded by debugging and observing its virtual memory that the instantiated object will open up space in the stack area or heap area in advance, and its member variables first use the low address in the stack area and then use the high address , (such as the structure, It is convenient to calculate the member position through the offset), so the memory storage data model can be drawn when the object d is instantiated:

        As can be seen from the previous article, the default construction first constructs its base class and then constructs its subclasses . For multiple inheritance, it is constructed in sequence according to the order of inheritance. Therefore, _b1 is instantiated first, _b2 is instantiated, and _d is instantiated next. , so it can be seen that instantiation is used from low address to high address! ! !

Use debugging to observe how it cuts and slices:

  1.  Assign the address of &d derived class Derive to the base class pointer of Base* p1, then it will slice and use _b1 for cutting, so the address pointed to by p1 at this time is the address of the instantiation object d of the original class Derive , but due to its Slices can only access the size of its base class backwards, and can only access _b1
  2. Assign the address of the &d derived class Derive to the Base* p2 base class pointer. At this time, slicing will be performed, and _b2 will be used for slicing. Since Base2 is instantiated in the middle, the slicing is assigned from the _b2 address when slicing , and it can only be accessed later Its base class size bytes, can only access _b2
  3. Assign the address of the &d derived class Derive to the pointer variable of its type. At this time, no slice occurs, and the address pointed to by p3 is the address of the entire instantiated object, so the address of p3 is the initial address ! ! !

Final Results:

 Although p3 and p1 have different offsets for backward access data, they point to the start address &d, _d1 of the same space, while p2 points to the same space based on the address of Base2 instantiation, that is, the _b2 address, and then according to the derived class member variable Memory distribution, you can get the above results! ! !

Summarize:

  1. For the assignment of derived class references to base classes, the bottom layer is the encapsulation of pointers and dereferences, with different meanings and the same memory operations! ! !
  2. For the direct assignment of the derived class to the base class, the split assignment will be performed directly

2. Complex diamond inheritance under multiple inheritance

Single inheritance: When a subclass has only one direct parent class, the inheritance relationship is called single inheritance

Multiple inheritance: When a subclass has two or more direct parent classes, the inheritance relationship is called multiple inheritance

 Diamond inheritance: Diamond inheritance is a special case of multiple inheritance.

What is ambiguity: (Multiple inheritance and diamond inheritance both cause ambiguity)

 As shown in the figure above: if class A and class B have multiple identical data members in multiple inheritance, the data members with the same name will cause ambiguity for class C, and they need to be distinguished through the class field, as shown in the following code :

	class A {
	public:
		A() :_a(1), _same(10) {

		}

		int _a;
		int _same;
	};
	class B {
	public:
		B() :_b(1), _same(1) {

		}
		int _b;
		int _same;
	};
	class C : public A,public B{
	public:
		void Print() {
			//cout << _same << endl;//err _same无法确定是属于哪个类,二义性
		}
		int _c;
	};
	void test() {
		C c;
		//cout << c._same << endl;//err _same无法确定是属于哪个类,二义性
	}

The problem of diamond inheritance: From the following object member model construction, it can be seen that diamond inheritance has data redundancy and ambiguity problems. In the Assistant object, there will be two Person members. In addition to ambiguity, if the indirect same base class member variables occupy too much space, memory space will also be wasted.

	class Person
	{
	public:
		string _name; // 姓名
	};

	class Student : public Person
	{
	public:
		int _num; //学号
	};

	class Teacher : public Person
	{
	public:
		int _id; // 职工编号
	};

	class Assistant : public Student, public Teacher
	{
	public:
		string _majorCourse; // 主修课程
	};

	void Test()
	{
		// 这样会有二义性无法明确知道访问的是哪一个
		Assistant a;
		//a._name = "peter";
		// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
		a.Student::_name = "xxx";
		a.Teacher::_name = "yyy";
	}

We found that there are two copies of the base class Person in the Assistant class, and the Assistant exists in the Student class and the Teacher class respectively. If there is a lot of data, it will seriously waste space and is not conducive to maintenance. We need to use domain operations to reference the data in the base class Person. character to distinguish.

In order to solve the above problems, C++ provides a virtual base class, also called the concept of virtual inheritance

3. Key points of rhombus virtual inheritance (virtual base class)

In order to solve the problems caused by the above-mentioned diamond-shaped inheritance, a virtual base class is introduced in C++. Its function is to retain only one base class member when indirectly inheriting a common base class. Virtual inheritance can solve the ambiguity and data redundancy of diamond-shaped inheritance . problem . As in the inheritance relationship above, the problem can be solved by using virtual inheritance when the Student and Teacher inherit Person. It should be noted that virtual inheritance should not be used in other places.

	class Person
	{
	public:
		string _name; // 姓名
	};
	class Student : virtual public Person
	{
	protected:
		int _num; //学号
	};
	class Teacher : virtual public Person
	{
	protected:
		int _id; // 职工编号
	};
	class Assistant : public Student, public Teacher
	{
	protected:
		string _majorCourse; // 主修课程
	};
	void Test()
	{
		Assistant a;
		a._name = "peter";
	}

Principle Analysis of Virtual Inheritance to Solve Data Redundancy and Ambiguity

3.1 Diamond non-virtual inheritance object storage model

In order to study the principle of virtual inheritance, a simplified diamond-shaped non-virtual inheritance system is given first, and then the model of object members is observed with the help of the memory window:

class A {
	public:
		int _a;
	};

	class B : public A{
	public:
		int _b;
	};
	class C : public A{
	public:
		int _c;
	};

	class D :public B, public C {
	public:
		int _d;
	};

	void Test() {
		D d;
		d.B::_a = 1;
		d.C::_a = 2;
		d._b = 3;
		d._c = 4;
		d._d = 5;
	}

Analyze the above rhombus non-virtual inheritance object storage model as follows:

The instantiated object d implements multiple inheritance, and its base classes B and C are non-virtual inheritance:

  1. First call the construction of the base class according to the first class it inherits, that is, call the construction of B, B inherits A, so B calls the construction of the base class A again
  2. In fact, the construction of the base class is called according to the second class it inherits, that is, the construction of C is called, and C inherits A, so C calls the construction of the base class A again
  3. Then according to the vs memory monitoring window, its virtual memory, observe its change order, and you can get its non-virtual diamond inheritance object storage model
  4. From the object storage model, it can be observed that class A in the non-virtual diamond inheritance has a copy in class B and class C respectively, thus causing ambiguity and data redundancy

3.2 Diamond virtual inheritance object storage model

In order to study the principle of virtual inheritance, a simplified diamond-shaped virtual inheritance system is given, and then the model of object members is observed with the help of the memory window:

	class A {
	public:
		int _a;
	};

	class B : virtual public A{
	public:
		int _b;
	};
	class C : virtual public A{
	public:
		int _c;
	};

	class D :public B, public C {
	public:
		int _d;
	};

	void Test() {
		D d;
		d.B::_a = 1;
		d.C::_a = 2;
		d._a = 0;
		d._b = 3;
		d._c = 4;
		d._d = 5;
	}

Analyzing the above rhombus virtual inheritance object storage model is as follows:

 The instantiated object d implements multiple inheritance, its base classes B and C are virtual inheritance, and class A is a virtual base class:

1. First, call the construction of its base class according to the first class it inherits, that is, call the construction of B. B virtually inherits A, and the data pointing to A in B becomes a pointer to the virtual base class table, which points to A virtual base class table, which stores the offset of the pointer to the memory where the public data is located , and then constructs the members of B

2. Secondly, call the structure of its base class according to the second class it inherits, that is, call the structure of C, C virtual inherits A, and the data pointing to A in C becomes a pointer to the virtual base class table, which points to A virtual base class table, which stores the offset of the pointer to the memory where the public data is located, and then constructs the members of C

3. Finally, construct the member variables of class D. At this time, instantiate the object d, and there is only one piece of data A in the d object, and the virtual base class table pointers of its two base classes .

 

simply put:

  • If non - virtual inheritance is used, then D will inherit two copies of the same data from B and C.
  • If virtual inheritance is used, then only one of the two identical data will exist in the D-class object . What D inherits from B and C is their unique data and the virtual base class table pointers of B and C. Through their respective virtual base class pointers, the offset between the pointer and the storage location of the public data can be obtained, and then it can be accessed.

3.3 Virtual Inheritance Object Storage Model

When it is not a diamond state transfer, about inheritance virtual, the object storage model of its virtual inheritance:

	class A {
	public:
		int _a;
	};

	class B : virtual public A{
	public:
		int _b;
	};
	class C : virtual public A{
	public:
		int _c;
	};

	class D :public B, public C {
	public:
		int _d;
	};

	void func(B* bb) {
		cout << bb->_a << endl;
	}
	void Test() {
		D d;
		d.B::_a = 1;
		d.C::_a = 2;
		d._a = 0;
		d._b = 3;
		d._c = 4;
		d._d = 5;
		B b;
		func(&d);
		func(&b);
	}

As long as it is virtual inheritance, the compiler will compile according to the virtual base class table pointer to ensure the correctness of its subclass transfer.

3.4 Multi-object inheritance relationship analysis of its virtual base class & virtual inheritance position

When there is polygon object inheritance, its virtual base class is generally the class to which the repeated member variables in the final inheritance relationship belong! ! !

 It can be seen from the inheritance relationship in the above figure that if the diamond-shaped inheritance is formed, the member variables of class A will be ambiguous in class E. Therefore, it is necessary to define virtual inheritance of classes B and C to solve their ambiguity and data redundancy.

5. Summary and reflection on inheritance

  1. Many people say that the syntax of C++ is complicated, but in fact, multiple inheritance is a manifestation. With multiple inheritance, there is diamond-shaped inheritance, and with diamond-shaped inheritance, there is diamond-shaped virtual inheritance, and the underlying implementation is very complicated. Therefore, it is generally not recommended to design multiple inheritance, and you must not design diamond inheritance. Otherwise, there are problems in complexity and performance.
  2. Multiple inheritance can be considered as one of the defects of C++, and many later OO languages ​​do not have multiple inheritance, such as Java.
  3. inheritance and composition
  •         Public inheritance is an is-a relationship. That is to say, every derived class object is a base class object.
  •         Composition is a has-a relationship. Assuming B composes A, there is an A object inside each B object.
  •         Prefer object composition over class inheritance.
  •         Inheritance allows you to define derived class implementations in terms of base class implementations. This kind of reuse by generating derived classes is often called white-box reuse. The term "white box" is relative to visibility: in inheritance, the internal details of the base class are visible to subclasses. Inheritance destroys the encapsulation of the base class to a certain extent, and the change of the base class has a great impact on the derived class. The dependency relationship between the derived class and the base class is very strong, and the coupling degree is high.
  •         Object composition is an alternative to class inheritance for reuse. New and more complex functions can be obtained by assembling or combining objects. Object composition requires that the objects being composed have well-defined interfaces. This style of reuse is called black-box reuse because the internal details of the objects are invisible. Objects only appear as "black boxes". There is no strong dependency relationship between composite classes, and the degree of coupling is low. Preferring object composition helps you keep each class encapsulated.

        In practice, use as many combinations as possible. The coupling degree of the combination is low, and the code maintainability is good. However, inheritance is also useful. Some relationships are suitable for inheritance, so use inheritance. In addition, to achieve polymorphism, inheritance is also necessary. The relationship between classes can use inheritance, you can use combination, use combination.

	// Car和BMW Car和Benz构成is-a的关系
	class Car {
	protected:
		string _colour = "白色"; // 颜色
		string _num = "陕ABIT00"; // 车牌号
	};

	class BMW : public Car {
	public:
		void Drive() { cout << "好开-操控" << endl; }
	};

	class Benz : public Car {
	public:
		void Drive() { cout << "好坐-舒适" << endl; }
	};

	// Tire和Car构成has-a的关系

	class Tire {
	protected:
		string _brand = "Michelin";  // 品牌
		size_t _size = 17;         // 尺寸

	};

	class Car {
	protected:
		string _colour = "白色"; // 颜色
		string _num = "陕ABIT00"; // 车牌号
		Tire _t; // 轮胎
	};

Guess you like

Origin blog.csdn.net/IfYouHave/article/details/131333938