Complex C++ Inheritance


Object-oriented three major features: encapsulation inheritance and polymorphism. We have already learned about encapsulation in the class and object stages. Encapsulation means not exposing the underlying implementation details and making it convenient for users under the premise of standard use. This article will explain the inheritance of this feature

what is inheritance

The word inheritance should be familiar to us. In real life, you, as one of your father's heirs, can obtain your father's property, directly saving N years of struggle. The inheritance here is almost the same meaning: there are many different classes in a program, but these classes may have common attributes (member variables or member methods), in order to avoid multiple declarations of the same member methods and member variables, C++ proposes inheritance. Inheritance is code reuse at the class design level . The inherited class is the parent class or base class, and the inherited class is called the subclass or derived class.
insert image description here

is aInheritance can be used when the relationship between the two is a relationship. In the above use case, Person is the parent class/base class, and Student is the subclass/derived class. It is best to match them when calling them, not base classes and subclasses .

public is a public inheritance method, and there are three inheritance methods like access qualifiers: public, protected, private

Inheritance

class members/inheritance public inheritance protected inheritance private inheritance
public members of the base class public members of derived classes protected members of derived classes private members of derived classes
protected members of the base class protected members of derived classes protected members of derived classes private members of derived classes
private members of the base class not visible in derived classes not visible in derived classes Not available in derived classes

The private members of the parent class are invisible in the subclass, not because they are not inherited, but because they cannot be accessed (unless shared functions are used)

The main difference between private and protected is inheritance. Since inheritance, we say that we should use private as little as possible (unless the member is unique to the class), because the purpose of inheritance is to reuse code.

In practice, public inheritance is generally used, and the other two methods are rarely used.

The default inheritance of classes defined by class is private, and the default inheritance of struct is public. But it is a good habit to specify the inheritance method

Assignment rules

Subclass objects can be assigned to parent class objects, parent class pointers, and parent class references, and there is no temporary variable generation in the middle. This assignment method is called slicing/cutting. (Only the subclass can be assigned to the parent class, but the parent class cannot be assigned to the subclass, because the subclass has the part of the parent class, but there are no subclass-specific members in the parent class.)

insert image description here
insert image description here

Slicing is limited to public inheritance, because if it is keep-alive or private inheritance, the member permissions in the parent class are changed in the subclass.

Scope in inheritance (hidden)

Both the parent class and the subclass have their own independent class fields. If they have member functions with the same name (not inherited), they will be hidden. The so-called hiding means that only the functions defined by the subclass can be seen in the subclass, and the same is true in the parent class. Why not function overloading? (Function overloading requires the same scope)

class Parent
{
public:
	void test(double i)
	{
		cout << "you can not see me"<<i << endl;
	}
protected:
	int _a;
};

class Child:public Parent
{
public:
	void test(int a)
	{
		cout << "这是一个隐藏示例" <<_a<< endl;
	}
private:
	int _b;
};

int main()
{
	Child b;
	b.test(1.1);

	return 0;
}

insert image description here

It can be seen that even if I pass a floating point number, it can only call the test function in the subclass, because b is a subclass object.

Of course, you need to call the test function of the parent class through the subclass object, because the subclass inherits the test function of the parent class, but it is hidden, and you can call the test function of the parent class as long as you add a scope qualifier:

insert image description here

Not only functions with the same name will be hidden, but also member variables with the same name will be hidden

class Child:public Parent
{
public:
	void test()
	{
		cout << "这是一个隐藏示例" <<_a<< endl;
	}
private:
	int _a = 3;
	int _b;
};

]

If there is a member function with the same name as the parent class in the subclass, the function of the parent class will be hidden by default, unless the domain access qualifier is used to indicate class domain access.

The destructors of the parent class and subclasses are also hidden, because the bottom layer uniformly handles the destructors as destructor functions

Do not define members with the same name in parent and subclasses, as this is confusing in itself

Default member functions in subclasses

The difference between an inherited subclass and a normal class is that the subclass still has the part of the parent class.

Constructor and destructor: do not process built-in types, call its construction and destructor for custom types

Copy construction and assignment overloading: copy by byte for built-in types, and call its copy construction and assignment overloading for custom types

The part of the parent class in the subclass should be processed by calling the default member function of the parent class.

class Child:public Parent
{
public:
	Child(int y)
		:Parent(_a)
		,_b(y)
	{}
    
    Child(const Child& c)
		:Parent(c)//直接在初始化列表处传子类对象调用父类拷贝构造即可(切片)
		, _b(c._b)
	{}
    
    Child& operator=(const Child& d)
	{
		if (this != &d)
		{
			Parent::operator=(d);//这里必须要指明类域,否则会因为隐藏的原因造成死循环
			_b = d._b;
		}
		return *this;
	}
    
private:
	int _b;
};

But the destructor is an exception, we cannot explicitly call the destructor of the parent class:

class Parent
{
public:
	Parent(int x)
		:_a(x)
	{}
	~Parent()
	{
		cout << "~Person" << endl;
	}
protected:
	int _a;
};

class Child:public Parent
{
public:
	Child(int y)
		:Parent(_a)
		,_b(y)
	{}

	~Child()
	{
		Parent::~Parent();//前面有提到过:析构函数默认是隐藏,要调用父类的析构要指明类域
		cout << "~Child" << endl;
	}
private:
	int _b;
};

insert image description here

Here the destructor of the parent class is called twice, because resources are not cleaned up here, otherwise an error will be reported.

We don’t need to call the parent class’s destructor. At the end of the subclass’s destructor, the compiler will automatically call the parent class’s destructor . Here we can take a look at it through assembly:

insert image description here

When you need to write your own default member function

1. The parent class does not have a default constructor, so you need to display and write the constructor yourself

2. There is a shallow copy of the subclass, and you need to write the assignment overload and copy construction yourself

3. If there are resources in the subclass to be released, the destructor needs to be explicitly written

Inheritance and Friends and Static Members

1. Friend relationship cannot be inherited

2. Although static members can be inherited, but because the static members are placed in the public code segment, the subclass and the parent class share the static members

multiple inheritance

A subclass inherits from a parent class is single inheritance, and a subclass inherits from multiple parent classes is multiple inheritance. There is nothing wrong with multiple inheritance itself, because it is also a very reasonable thing for a class to inherit multiple classes. But multiple inheritance gives others the opportunity to make mistakes. This mistake is diamond inheritance

diamond inheritance

insert image description here

It can be seen that this inheritance relationship is like a diamond, so it is called diamond inheritance

The problem with rhombus inheritance

Because Studentand Teacherare Personboth subclasses, that is to say, both classes have a Personmember variable; and Assistantas a common subclass of these two classes, they also inherit two Personmembers, which will cause data redundancy. In addition, it will cause ambiguity when calling (I don’t know whether you want to call the StudentPerson member in the class or Teacherthe Person member in the class).

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

Let's take a look at the memory structure inherited by the diamond through 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;
};

int main()
{
	D d;
	
	d._b = 1;
	d._d = 2;
	d.B::_a = 3;
	d.C::_a =4;

	return 0;
}

insert image description here

Diamond Virtual Inheritance

virtualThe root cause of the data redundancy and ambiguity problems is that there are two copies of the same Person member data in the Assistant, and this problem can be solved by adding keywords when inheriting. Let's take a look at the memory structure of diamond virtual inheritance through 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;
};

insert image description here

After using virtual inheritance, _athere is only one copy, so there is no problem of ambiguity, but there is a string of addresses above _band _cabove inherited from class B and class C. Once again, we need to search for this string of addresses through memory, and find that a number 0x14 is stored in the position after this string of addresses. This number is the offset of the members inherited from B. Through this offset, object d can reach dB::_a _a. This solves the problem of redundancy of diamond-shaped inheritance members.

At this time, A is called a virtual base class, and the inherited classes B and C must use the offset in the virtual base table to find the members in A.

In actual use, don't design diamond-shaped inheritance, because this is a big pit of C++, and it is basically impossible to climb out if you jump into it.

inheritance and composition

Inheritance is a is arelationship, which means that each subclass is a parent class; composition is has aa relationship, that is to say, there is a class A object in each class B. Here is an example 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; // 轮胎
};

Inheritance is a kind of white-box reuse. The subclass can see the implementation details of the parent class, and the coupling between the parent class and the subclass is high. Once the parent class is modified, it may affect the normal use of the subclass. Composition is a kind of black-box reuse. It is impossible to spy on the details of its internal implementation, and the coupling degree of the composition is low. Only the modification of the public members of Car will affect the use of BMW.

If a class is both is aa relationship and has aa relationship, composition is preferred.

Guess you like

Origin blog.csdn.net/m0_62633482/article/details/130848025