[Road to Advanced C++] Inheritance

Preface

Earlier we talked about the first major feature of object-oriented- encapsulation , and then we have to face the second major feature- inheritance . So what is inheritance? From a functional perspective, it is reuse .
For example: We all have it 人的特性(性别,外貌,身份证号), so in society, we may still 学生、老师、工人wait for information with identity significance. If we need to describe the characteristics of the person when describing the students, it would be a bit too cumbersome. Therefore, students, teachers , the worker can obtain 复用the person's characteristic information, and then add the corresponding identity-specific information on this basis.

1. Concept

We can simply understand inheritance through examples earlier. Let’s talk about the specific definition below:

  • The inheritance mechanism is the most important means of object-oriented programming to enable code reuse .
  • Inheritance allows programmers to extend and add functions while maintaining the characteristics of the original class , thus generating a new class, called a derived class .
  • Inheritance is the reuse of class design levels .

Next, let’s talk about inheritance from the perspective of space . The specific division of an object can be divided into:

  1. Member function - constant area
  2. Non-static member variables - depends on the scope of class instantiation and the location of memory application (global: static area, local: stack area, heap area: dynamic application of memory)
  3. Static member variables - static area

How to verify spatial inheritance? Let’s talk about the usage of inheritance first, and we will prove it later.


Let's understand how to define an inherited class from the perspective of permissions :

Insert image description here

  • There are three inheritance methods - public inheritance (most commonly used), protected inheritance and private inheritance (less commonly used).
  • Note: If the inheritance method is not written, the default is private inheritance.

So what is the use of inheritance? A table can explain:

Insert image description here
Summarize:

  • Regardless of inheritance, private members of the base class are not visible in the derived class .
  • Inheritance method, inheritance is based on the lowest access rights . For example, the public members of the base class adopt protected inheritance. After inheritance, the public members of the base class are protected members of the derived class.
  • If you do not write the inheritance method, the default is private inheritance.

The difference between protected members and private members: protected members of the base class can also be used in the class through the derived class after inheritance, while private members cannot be accessed.

At this point we will complete the above proof:

Code:

class A
{
    
    
public:
	void func()
	{
    
    
		cout << "A::func()"<<endl;
	}
	int _a = 1;
	static int _c;
};

int A::_c = -1;
class B : public A
{
    
    

public:
	int _b = 0;
};
int main()
{
    
    
	A a;
	B b;
	//成员函数
	a.func();
	b.func();
	cout << endl;

	//非静态的成员变量
	cout << &a._a << endl;
	cout << &b._a << endl << endl;

	//静态的成员变量
	cout << &a._c << endl;
	cout << &b._c << endl;

	return 0;
}
  • Debugging and viewing the disassembled function address:
    Insert image description here
    Conclusion: A function is called, so the member function inherits the usage rights.

Let’s run the code again:

Insert image description here

  • Conclusion: Non-static member variables inherit a copy of the member variable. Static member variables inherit usage rights.

Summarize:

Member functions and static member variables inherit usage rights. Non-static member variables inherit a copy of the member variable.

2. Nature

1. Assignment conversion

Before talking about assignment conversion, we must first understand the usage of inherited references and pointers.

class A
{
    
    
public:
	void func()
	{
    
    
		cout << "A::func()"<<endl;
	}
	int _a = 1;
	static int _c;
};

int A::_c = -1;
class B : public A
{
    
    

public:
	int _b = 0;
};
int main()
{
    
    
	B b;

	int i = 0;
	int& ri = i;
	const double& di = i;

	A& rb = b;
	A* rptr = &b;

	return 0;
}

We have said before that temporary variables will be generated between different types of references , and temporary variables have constant attributes , so when i is converted to double here, a temporary variable will be generated, so const needs to be added.

However, the inherited class is converted to a base class and no temporary variables are generated, so const is not added. This phenomenon is called upward conversion, which means that a subclass can be converted to a parent class.

principle:
Insert image description here

  • It is equivalent to the reduction of authority, that is, cutting.

expand:

A base class pointer to a derived class can be converted to a pointer to a subclass through safe conversion, thereby achieving downcasting.

class A
{
    
    
public:
	void func()
	{
    
    
		cout << "A::func()"<<endl;
	}
	int _a = 1;
	static int _c;
};

int A::_c = -1;
class B : public A
{
    
    

public:
	int _b = 0;
};
int main()
{
    
    
	B b;
	A* rptr = &b;
	B* rrptr = static_cast <B*>(rptr);
	//B* rrptr = dynamic_cast <B*>(rptr);
	//这个是虚函数才能使用的(多态会讲)。
	return 0;
}
  • Note: The prerequisite for converting references and pointers is public inheritance, otherwise conversion cannot be done.

After understanding this, assignment conversion is not difficult to understand.

class A
{
    
    
public:
	void func()
	{
    
    
		cout << "A::func()"<<endl;
	}
	int _a = 1;
	static int _c;
};
int A::_c = -1;
class B : public A
{
    
    

public:
	int _b = 0;
};
int main()
{
    
    
	B b;
	A a;
	a = b;
	//编译器生成的为 A & A::operator = (const A &);
	
	//b = a;报错,因此引用支持向下转换,不支持向上转换。
	//且无法进行强制类型转换。
	return 0;
}
  • Assignment conversion uses reference syntax to cut subclasses.

2. Scope-hide/redefine

In order to understand the nature of scope, I list a piece of code for easy understanding:

class  Person
{
    
    
public:
	int func(int i)
	{
    
    
		cout << "Person::func" << endl;
	}

	int _num = 0;

};

class Student : public Person
{
    
    
public:
	void func()
	{
    
    
		cout << "Student::func" << endl;
		cout << _num << endl;
	}
	int _num = 1;
};

int main()
{
    
    
	Student stu;
	stu._num = -1;
	stu.func();
	//stu.func(1);
	return 0;
}

The result of running the code is:
Insert image description here

  • Why won't an error be reported?
  • Why is the result like this?
  • Does func here constitute overloading?
  • What will happen if you uncomment the code? Why?

We must first understand that the scope is the scope that the compiler searches, and the scope includes the local domain, the global domain, the namespace domain, and the class domain. The function of the scope qualifier is to specify the search in a certain domain, otherwise an error will be reported.

Then we will explain why no error is reported here, because the inherited things are not in the same class domain, and because different class domains exist independently, they will block each other, so we generally call it blocking caused by the same name . The phenomenon is called hiding or redefinition.

Understand these two concepts, let's analyze the second question . First, assign -1 to _num. First, search in the Student's class domain. If found, stop. Obviously, here is assigning a value to Student's _num , and then calling func function , in the same way, first search for Student, if found, stop. Found here, the func function in Student is called, and finally search for _num. Obviously, it is found 首先再当前局部域查找,如果没有就在当前类域进行查找,如果还没有就在基类的类域进行查找,如果还没有就在全局域进行查找,如果还找不到就报错。in the current class domain, so it is Student _num in the class field.

With the concept of hiding/redefining, the func here obviously does not constitute an overload, because the overload requires that it be in the same class domain!

Finally, releasing the commented code will result in a compilation error, because what 编译器很懒,它找到就不再找了is being searched for here is still the Student's func.

3.Default member function

①Constructor

class A
{
    
    
public:
	A()
		:_a(1)
	{
    
    }
int _a;
};
class B : public A
{
    
    
public:
	B()
		:_b(0)
	{
    
    }
int _b;
};
int main()
{
    
    
	B a;
	return 0;
}

Debugging f11 statement-by-statement run:

Insert image description here

  • It is not difficult to see that when calling the constructor of a subclass, the constructor of the parent class is first called to construct the members of the derived class, and then the constructor of the subclass is called to construct the members of the subclass.

Why is it designed like this?
Personal understanding: 总不能一个人干两份活吧?你干你的,我干我的,这样分工比较明确As for the order, it may be because 子类的成员可能会用父类成员的一些值初始化.

One point to note: If the initialization list of the subclass does not display the constructor of the parent class, the default constructor will be called. If not, an error will be reported. This also means that if the parent class does not have a default constructor, the parent class must be explicitly called in the subclass. Class constructor.

Example:

class A
{
    
    
public:
	A(int val)
		:_a(val)
	{
    
    }
int _a;
};
class B : public A
{
    
    
public:
	B()
		:_b(0)
		,A(1)
	{
    
    }
int _b;
};
int main()
{
    
    
	B a;
	return 0;
}
  • It should be emphasized that the order of initialization has nothing to do with the order of the initialization list. Here, the constructor first calls the constructor of A in the initialization list, and then goes through the initialization list of the subclass.

②Copy structure

class Person
{
    
    
public:

	Person(const char* name = "张三", int age = 18)
	{
    
    
		_name = name;
		_age = age;
	}
	Person(const Person& per)
	{
    
    
		_name = per._name;
		_age =  per._age;
	}
private:
	string _name;
	int  _age;
};

class Student :public Person
{
    
    
public:
	Student(const char* name = "张三", int age = 18,int id = 12345)
		:Person(name,age)
		,_id(id)

	{
    
    
		_id = id;
	}
	Student(const Student& stu)
		:Person(stu)
	{
    
    
		_id = stu._id;
	}
private:
	int _id;
};

int main()
{
    
    
	
	Student stu2("李四",19,8888);
	Student stu1 = stu2;
	return 0;
}
  • There is not much difference between the copy constructor and the constructor. During the implementation process, especially when the copy constructor of the subclass explicitly calls the constructor of the parent class, upward conversion (reference) will occur. This is very important!

  • Another point to emphasize is that if you do not explicitly call the copy constructor, the default constructor will be called, but this may not complete the copy effect. If there is no default constructor, an error will be reported!

Final summary: The calling order of constructors is first parent and then child.

③Destructor

class A
{
    
    
public:
	~A()
	{
    
    }
};
class B : public A
{
    
    
public:
	~B()
	{
    
    }
};
int main()
{
    
    
	B b;
	return 0;
}

Commissioning:
Insert image description here

  • It is not difficult to see that to destruct a subclass, first call the destructor of the subclass, and then call the destructor of the parent class .

Think about why this is the case?

This has something to do with the constructor. When we constructed it, we mentioned that we should construct the member variables of the parent class first, and then the member variables of the subclass. This is to increase the information flexibility of the subclass and allow the members of the subclass to follow The parent class is involved. If it is involved, if the parent class is destructed first, the data of the members of the subclass and the parent class will be invalid and cannot be used. If it is used again during the destruction of the subclass, it may Dangerous behaviors such as out-of-bounds behavior will occur, so it is necessary to destruct the members of the subclass first, and then destruct the members of the parent class.

④Assignment overloading

  • modern writing
class A
{
    
    
public:
	A(int a = 0, int b = 0)
		: _a(a)
		, _b(b)
	{
    
    }
	void swap(const A& a)
	{
    
    
		_a = a._a;
		_b = a._b;
	}
	A& operator = (A a)
	{
    
    
		swap(a);
		return *this;
	}
private:
	int _a;
	int _b;

};
class B : public A
{
    
    
public:
	B(int a = 0, int b = 0, int c = 0, int d = 0)
		:A(a,b)
		,_c(c)
		,_d(d)
	{
    
    }
	void swap(const B& b)
	{
    
    
		A::swap(b);
		_c = b._c;
		_d = b._d;
	}
	B& operator =(B b)
	{
    
    
		swap(b);
		return *this;
	}
	int _c;
	int _d;
};
int main()
{
    
    
	B b1(1,2,3,4);
	B b2(4,3,2,1);
	b1 = b2;
	return 0;
}
  • Ordinary writing method - the interface generated by the compiler by default is written like this
class A
{
    
    
public:
	A(int a = 0, int b = 0)
		:_a(a)
		, _b(b)
	{
    
    }
	A& operator = (const A& a)
	{
    
    
		if (this != &a)
		{
    
    
			_a = a._a;
			_a = a._b;
		}
		return *this;
	}
private:
	int _a;
	int _b;

};
class B : public A
{
    
    
public:
	B(int a = 0, int b = 0, int c = 0, int d = 0)
		:A(a,b)
		,_c(c)
		,_d(d)
	{
    
    }
	B& operator =(const B& b)
	{
    
    
		if (this != &b)
		{
    
    
			//因为基类的私有成员,在派生类中不可见,所以我们需要调用基类的赋值进行拷贝。
			A::operator =(b);
			_c = b._c;
			_d = b._d;
		}
		return *this;
	}
	int _c;
	int _d;
};
  • The most inheritance is redefinition + upconversion

4. Friend function

C++11 标准不允许友元函数的声明有默认参数,除非友元声明是一个定义
Personal understanding: In order to prevent inappropriate parameters, such as anonymous objects of undefined classes, an error may be reported directly.

class B;
class A
{
    
    
	friend void func(const A& a,  const B& b);
public:

private:
	int _a = 1;
};

class B :public A
{
    
    
public:

private:
	int _b = 2;
};
void func(const A& a, const B& b = B())
{
    
    
	cout << a._a << endl;
	cout << b._a << endl;
	//cout << b._b << endl;
}
int main()
{
    
    
	B b;
	func(b,b);
	return 0;
}

Code results:
Insert image description here

Uncomment the code:
Insert image description here

  • Conclusion: Friend classes cannot be inherited by subclasses. Friends are limited to breaking through the scope of the parent class. That is to say, the friend function can access the parent class in the subclass , that is, the scope is in the parent class, and the child class The scope of the class is not accessible.

Summary: Friend relationships cannot be inherited, which means that base class friends cannot access the private and protected members of the subclass , but the private and protected members of the base class in the subclass can be accessed through the friends of the base class.

5. Multiple inheritance

①Single inheritance——“One father, many sons”

  • The parent class has at least one derived class, but the derived class can only have one parent class

It can be like this:
Insert image description here

Or this would count:
Insert image description here

②Multiple inheritance - "one son with many fathers"

  • A subclass has multiple parent classes.
    Insert image description here

③Diamond inheritance - "One son with many fathers, many fathers with one father"

  • Special cases derived from multiple inheritance.
    Insert image description here
class Person
{
    
    
protected:
	string _sex;
};
class Student : public Person
{
    
    
protected:
	int _id;
};
class Teacher : public Person
{
    
    
protected:
	string _profession;
};
class Assistant : public Student,public Teacher
{
    
    
protected:
	int _age;
};
int main()
{
    
    
	Assistant a;
	a._sex = 1;
	return 0;
}

Object model:
Insert image description here

Description: Multiple inheritance inherits from left to right. Single inheritance inherits from the parent class first. That is to say, multiple inheritance starts from left to right and draws the object model from top to bottom. Single inheritance starts from top to bottom, drawing the parent class first and then the subclass.

  • Obviously, the first question is, a person cannot have two genders, right? This results in data redundancy .
  • The second problem is that the above code will report an error because it does not know which _sex is being accessed and the scope must be specified, which leads to ambiguity .

So how to solve this problem?

④Diamond virtual inheritance

How to achieve this?

class Person
{
    
    
public:
	int _number = 10;
};
class Student : virtual public Person
{
    
    
public:
	int _id = 6;
};
class Teacher :virtual public Person
{
    
    
protected:
	int _telephone= 1;
};
class Assistant : public Student, public Teacher
{
    
    
public:
	int _age = 18;
};
int main()
{
    
    
	Assistant a;
	return 0;
}
  • Add virtual before the inheritance method of the first-generation derived class derived from the base class - virtual inheritance

So what is its principle of solving data redundancy and ambiguity?

The first step is to draw the object model:
Insert image description here
we can see that the original location where the Person is stored has changed, and is replaced by data similar to the address.

Let's verify again whether it is what we thought.
Insert image description here

  • Apparently yes. The Person position is calculated through the offset and accessed.
  • We can also observe that the offsets are arranged from top to bottom according to the object model of the class .

Note: The first position pointed to by the virtual base table pointer here is precisely 虚基表指针的地址相对于this指针的偏移量。the offset of the address of the virtual table pointer relative to the parent class.具体用途多态会讲。

Someone will ask why we need to use a table to store the offset instead of directly placing the Person's address at the original location . The answer is actually very simple. If you want to know the Person's address, don't you also have to perform calculations? Moreover, if each instantiated class is calculated, it will inevitably lose efficiency, but instantiated classes have a characteristic that the relative position will not change ! Therefore, we only need to calculate it once , and then calculate it directly based on the relative position. The instantiated classes can share one copy , which also improves efficiency .

One more thing, so that it's less error-prone when upcasting .

int main()
{
    
    
	Assistant a;
	Student stu;
	Student& stu1 = a;
	stu1._number = 1;
	//基类的引用,访问父类的虚基表,得到偏移量1,从而访问Person。
	stu._number = 2;
	//基类的对象,访问基类的虚基表,得到偏移量2,从而访问Person。(偏移量1 != 偏移量2)
	return 0;
}
  • This access method is the same, and due to the correction of the offset, the correct base class can be accessed!

3. Summary

  • Inheritance is complicated by the diamond-shaped virtual inheritance caused by multiple inheritance . OO languages ​​such as Java have abandoned multiple inheritance.

Description: OOP - Object Oriented Programming (object-oriented programming)

  • The disadvantage of inheritance is that it increases coupling .
    For example:
    1. Black box testing : If we don’t know the implementation, we only know its functions, then we only need to perform functional tests.
    2. White box testing : The implementation is exposed and its functions are known, so we have to understand its implementation before we can test it.
    继承,更像是一种白盒测试, the protect members we inherit from the base class can still be used. Once the member names of the base class are changed, the members of the derived class will become unusable, which is why the coupling degree is improved.
    组合,更像是一种黑盒测试, I only use functions, I don’t care about your underlying details, so even if your details change, it has no impact on me, which reduces the degree of coupling.
    Summary: 组合更符合高内聚,低耦合的概念. Therefore we prefer to use combinations. As for inheritance, it should be analyzed in specific scenarios before using it, especially multiple inheritance and diamond virtual inheritance!

Description:
High cohesion—— 一心只干一件事.
Low coupling - 不同功能的关联程度很小.

Supplement - composition and inheritance:

class A
{
    
    
protected:
	int _a;
};
class B :public A
{
    
    
	//继承	
};
class C
{
    
    
	class A;//组合
};
  • Inheritance is more like one is_a关系,即花是植物, while composition is more like that has_a的关系,即车里面有轮胎.

 That’s it for today’s sharing. If you think the article is good,Give it a like and encourage it.! Us 下篇文章再见!

Guess you like

Origin blog.csdn.net/Shun_Hua/article/details/131927122