C++: Template advancement and inheritance

Template advanced

1. Non-type template parameters

Template parameters: type parameters and non-type parameters.

Type parameter: Appears in the template parameter list, follows class or typename parameter type name.

Non-type parameter: It is to use a constant as a parameter of the class (function) template, which can be used in the class (function) template < a i=7>Use this parameter as a constant.

// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
    
    
public:
	T& operator[](size_t index) {
    
     return _array[index]; }
	const T& operator[](size_t index)const {
    
     return _array[index]; }

	size_t size()const {
    
     return _size; }
	bool empty()const {
    
     return 0 == _size; }

private:
	T _array[N];
	size_t _size;
};

Notice:

  1. Non-type parameters are rarely used, because can only be given to integer, and an error will be reported if given to double or a series of custom types.
  2. Non-type template parameters must determine the result during compilation, so constants must be given.


2.Specialization of templates

2.1 The concept of specialization

Normally, using templates can implement some type-independent code, butFor some special types, you may get some wrong results and require special processing , such as the following situation:

//拿日期类举例,我们比较日期类大小要比的是内容
//指针的比较是没有意义的!!!
template<class T>
bool Less(T left, T right)
{
    
    
	return left < right;
}
int main()
{
    
    
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果不确定
	return 0;
}

2.2 Function template specialization

Requirements for function template specialization:

  1. Musthave a basic function template first.
  2. The keyword template is followed by a pair of angle brackets <>. Template parameters can also be added inside, which can be used for partial specialization.
  3. The function name is followed by a pair of angle brackets<>,The angle brackets specify the type that needs to be specialized.
  4. The function parameter list must be exactly the same as the basic parameter type of the template function.You cannot add more parameters or change the parameter positions.
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
    
    
	return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
    
    
	return *left < *right;
}
int main()
{
    
    
	cout << Less(1, 2) << endl;
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl;
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 这里调用的就是特化的版本,不是指针而是内容比较
	return 0;
}

2.3 Class template specialization
//类模板特化和函数模板相似,只是加<>的位置变成了类名后面加
template<class T1, class T2>
class Data
{
    
    
public:
	Data() {
    
     cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int, char>  //特化版本
{
    
    
public:
	Data() {
    
     cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};
void TestVector()
{
    
    
	Data<int, int> d1;  //这个会通过模板生成更加合适的
	Data<int, char> d2;  //这个特化版本最合适
}

2.4 Full specialization and partial specialization
2.4.1 All specialization

Full specialization is to determine all the parameters in the template parameter list. What we wrote before is full specialization. .

2.4.2 Partial specialization

Partial specialization has the following two expressions:

  • Partial specialization: Specialize onepart of the parameters in the template parameter class table
template<class T1, class T2>
class Data
{
    
    
public:
	Data() {
    
     cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

// 对模板参数列表的一部分特化,这个还是比较好理解的
// 将第二个参数特化为int,注意特化必须保证有一个基础的函数模板
template <class T1>
class Data<T1, int>
{
    
    
public:
	Data() {
    
     cout << "Data<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};
  • Parameter restrictions: restrictions on templatesParameter types
template<class T1, class T2>
class Data
{
    
    
public:
	Data() {
    
     cout << "Data<T1, T2>" << endl; }
};

//只要是指针,不管你指向什么类型你都匹配我
//两个参数偏特化为指针类型
template <class T1, class T2>
class Data <T1*, T2*>
{
    
    
public:
	Data() {
    
     cout << "Data<T1*, T2*>" << endl; }
};


//只要是引用,不管你引用什么类型你都匹配我
//不过其实和指针是一回事,引用底层就是指针
template <class T1, class T2>
class Data <T1&, T2&>
{
    
    
public:
	Data() {
    
     cout << "Data<T1&, T2&>" << endl; }
};

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
    
    
public:
	Data() {
    
     cout << "Data<T1, int>" << endl; }
};

int main()
{
    
    
	Data<double, int> d1; // 调用特化的int版本
	Data<int, double> d2; // 调用基础的模板生成合适的 
	Data<int*, int*> d3; // 调用特化的指针版本
	Data<int&, int&> d4; // 调用特化的引用版本
	return 0;
}



3. Separate compilation of templates

Let me start with the conclusion: Generally speaking, templates do not recommend separation of declarations and definitions. If necessary, try to separate them at the same time. Separate within a file.

3.1 Separation of files
namespace My
{
    
    
	template<class T>
	class A
	{
    
    
	public:
		typedef T* ptr;  //T类型指针
		ptr operator&();  //取到T类型指针

		T get();
	private:
		T _a;
	};
}

//写简单一点,就分离两个函数好了
//分离编译的时候A<T>还没有完全实例化
//不知道ptr是类型还是静态成员,所以需要加typename指定它是类型
//另外需要用类域限定符指定该函数(类型)属于那个空间中的那个类
template<class T>
typename My::A<T>::ptr My::A<T>::operator&()  //取到T类型指针
{
    
    
	return &_a;
}

template<class T>
T My::A<T>::get()
{
    
    
	return _a;
}

3.2 Separation under different files

In C/C++ programs, each source file is independent of each other before linking, and templates require that the template parameter type be determined before compilation< a i=2>, the template will be instantiated, and the biggest problem with divided files is that the separated part cannot determine the parameter type (T), and It cannot be instantiated.

//a.h,这里写一起是方便看,实际在不同文件
namespace My
{
    
    
	template<class T>
	class A
	{
    
    
	public:
		typedef T* ptr;  //T类型指针
		ptr operator&();  //取到T类型指针

		T get();
	private:
		T _a;
	};
}

//a.cpp
#include "a.h"
//这里因为A<T>还没有完全实例化,可以理解为实例化过程中分离的部分和内部是隔离的
//也就是说不知道指定的内容是类型还是静态变量,需要加typename指定它是类型
template<class T>
typename My::A<T>::ptr My::A<T>::operator&()  //取到T类型指针
{
    
    
	return &_a;
}

template<class T>
T My::A<T>::get()
{
    
    
	return _a;
}

//test.cpp
#include "a.h"
int main()
{
    
    
	My::A<int> a;
	a.get();  //调了一下这个分离的函数
	return 0;
}

Let’s look at the execution results:
Insert image description here
The error reported here is a link error, which is actually the get function.The function is not instantiated at allCome out, we can solve this problem by displaying instantiation.

//a.cpp
template<class T>
typename My::A<T>::ptr My::A<T>::operator&()  //取到T类型指针
{
    
    
	return &_a;
}

template<class T>
T My::A<T>::get()
{
    
    
	return _a;
}

template
My::A<int>;  //告诉了T类型,你去实例化

However, display instantiation is very limited. Different types must be written once. If you want to separate them, I still recommend the first way of writing.



inherit

1. Concept and definition of inheritance

1.1 The concept of inheritance

The inheritance mechanism is the most important means of object-oriented programmingto enable code reuse. It allows programmers to /span> of object-oriented programming and embodies the cognitive process from simple to complex. In the past, the reuse we came into contact with was function reuse, and inheritance is reuse at the class design level. presents the hierarchical structure and add functions, thus generating a new class, which is called a derived class. InheritanceExpand on the basis of maintaining the characteristics of the original class
Insert image description here

1.2 Definition of inheritance
1.2.1 Define format

Insert image description here

1.2.2 Inheritance relationship and access qualifiers

Let’s talk about it hereMost inheritance is public inheritance, in C++protected This access qualifier is prepared for inheritance.

Insert image description here

We present these nine situations in a table, but everyone does not need to memorize this table, just look at Summary 1 and 2:< /span>
Insert image description here
Summary:

  1. Private members of the base class are not visible in the derived class anyway (invisible: ① cannot be accessed outside the class ② invisible, cannot be accessed inside the derived class, so it is inherited).
  2. Other members of the base classAccess mode in the derived class == Min (Members inAccess qualifier of the base class , Inheritance method), that is, whoever has the smallest authority takes it, considered public > protected > private.
  3. If we want the derived class to be like most classes, we can modify the members of the base class that we want others to access with public, and those members that we don’t want others to access with protected. Finally Just use public inheritance.
  4. The inheritance method does not have to be written. When using the keyword class, the default inheritance method is private, and when using struct, the default inheritance method is public.But it is best to write it explicitly. Inheritance mode.


2. Object assignment conversion of base class and derived class

  • Derived class objects can be assigned to ①base classobject ②base classpointer a> ③Reference of base class. There is a vivid saying here called slicing or cutting. The implication is to cut out the parent class part of the derived class and assign it to .
  • Base class objects cannot be assigned to derived class objects (The derived class has the base class part, but the base class does not have the derived class part).
    Insert image description here
class Person
{
    
    
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
    
    
public:
	int _No; // 学号
};
void Test()
{
    
    
	Student sobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//2.基类对象不能赋值给派生类对象
	sobj = pobj;

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &sobj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;

	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
	ps2->_No = 10;
}


3. Scope in inheritance

  1. In the inheritance systembase class and derived class All haveindependent scopes.
  2. There are members with the same name in the derived class and the base class, and the derived class members will be shielded from the parent class Direct access to members with the same name, this situation is called hiding, also called redefinition. (In derived class member functions, you canuse base class::base class member for explicit access)
  3. It should be noted that if it is the hiding of a member function, onlythe function name is the same to constitute hiding.
  4. Note that in practice it is best not to define members with the same name in the inheritance system.

Class member variables have the same name:

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
    
    
protected:
	string _name = "小李子"; // 姓名
	int _num = 111;  //身份证号
};

class Student : public Person
{
    
    
public:
	void Print()
	{
    
    
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl; // 类名::成员显示访问
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号
};
void Test()
{
    
    
	Student s1;
	s1.Print();
};

The class naming function has the same name:

//这里要重点区分一下函数重载,首先重载是对同一域中才有的概念,这里肯定不构成重载
//其次隐藏的要求也和重载不同,只要和基类成员函数同名就构造隐藏
class A
{
    
    
public:
	void fun()
	{
    
    
		cout << "func()" << endl;
	}
};
class B : public A
{
    
    
public:
	void fun(int i)
	{
    
    
		A::fun();  //类名::成员 还是可以访问的
		cout << "func(int i)->" << i << endl;
	}
};

void Test()
{
    
    
	B b;
	b.fun(10);
};


4.Default member functions of derived classes

Here mainly talks about construction, =overloading and destruction, and the remaining & overloading is of little significance. There is a principle when dealing with these functions of subclasses:Treat parts of the parent class as a whole, call the functions of the parent class to process them, and process parts of the subclasses as subclasses .

4.1 Construction and copy construction
  1. Because of the existence of the initialization list,Even if you do not explicitly call the parent class constructor when writing the subclass constructor, the parent class constructor will call itself to complete the initialization.
  2. If you need to specify the content of the initialized parent class, you can display the parent class structure.
class Person
{
    
    
public:
	Person(const char* name = "peter")
		: _name(name)
	{
    
    
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
    
    
		cout << "Person(const Person& p)" << endl;
	}

protected:
	string _name; // 姓名
};
class Student : public Person
{
    
    
public:
	Student(const char* name, int num)
		: Person(name)
		, _num(num)
	{
    
    
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
	{
    
    
		cout << "Student(const Student& s)" << endl;
	}

protected:
	int _num; //学号
};
void Test()
{
    
    
	Student s1("jack", 18);
	Student s2(s1);
	Student s3("rose", 17);
}

4.2operator = ()
  1. When encountering an object with resources on the heap, in order toavoid multiple releases, we may need to write operator= ourselves.
  2. Different from construction, if we explicitly write operator= in the subclass,The operator= of the parent class will not be called automatically and must be called explicitly< a i=2>.
class Person
{
    
    
public:
	Person& operator=(const Person& p)
	{
    
    
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
    
    
public:
	Student& operator = (const Student& s)
	{
    
    
		cout << "Student& operator= (const Student& s)" << endl;
		Person::operator =(s);  //不会自动调用,必须显示调用
		_num = s._num;
		return *this;
	}
protected:
	int _num; //学号
};

4.3 Destructor
  1. Destruction is special and cannot be called actively. must be automatically called by the compiler. must be called after the subclass destructor. You won't get an error if you adjust it yourself, but it won't take effect either.
  2. As for why it is designed this way? ①Keep the order of destruction first when defined later. ②The child can use the parent. If the parent is destructed first, the child may access the members of the parent, resulting in wild pointer access.
  3. ThereforeWhen designing subclass destruction, you only need to ensure that your own resources are released correctly.



5. Friends and static members in inheritance

  • Friend relationships cannot be inherited, which means that base class friends cannot access private and protected members of subclasses
class Student;
class Person
{
    
    
public:
	friend void Display(const Person& p, const Student& s);  //声明该函数是基类的友元
protected:
	string _name; // 姓名
};
class Student : public Person
{
    
    
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
    
    
	cout << p._name << endl;    //你是基类的友元,访问基类对象是可以的
	cout << s._stuNum << endl;  //友元不继承你不能访问派生类对象,这里会报错,显示不可访问
}

int main()
{
    
    
	Person p;
	Student s;
	Display(p, s);
	return 0;
}
  • The base class defines static members, then there is only one such member in the entire inheritance system. No matter how many subclasses are derived, there is only one static member instance.
//无论继承多少层,大家用的始终是同一个变量
class Person
{
    
    
public:
	Person() {
    
     ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

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

class Graduate : public Student
{
    
    
protected:
	string _seminarCourse; // 研究科目
};
void TestPerson()
{
    
    
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}



6. Diamond inheritance

6.1 The concept of diamond inheritance
  • Single inheritance: a subclassWhen there is only one direct parent class, this inheritance relationship is called single inheritance
    Insert image description here
  • Multiple inheritance: When a subclass has two or more direct parent classes, this inheritance relationship is called multiple inheritance

Insert image description here

  • Diamond inheritance: Diamond inheritance isa special case of multiple inheritance.
    Insert image description here

6.2 Dangers of Diamond Inheritance

The problem of diamond inheritance: From the object member model structure below, we can see that diamond inheritance has data redundancy and ambiguity question. In the assistant object there will be two copies of the person member.

Insert image description here

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

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

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

class Assistant : public Student, public Teacher
{
    
    
protected:
	int _score; // 主修课程
};
void Test()
{
    
    
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";   //这个地方会报错,编译器不知道访问那个
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

6.3 Solution to diamond inheritance
  1. Virtual inheritance can solve the problems of ambiguity and data redundancy of diamond inheritance. As shown in the inheritance relationship above, using virtual inheritance when student and teacher inherit person can solve the problem. It should be noted that virtual inheritance should not be used elsewhere.
  2. The keyword for virtual inheritance isvirtual.
class Person
{
    
    
public:
	string _name; // 姓名
};

class Student : virtual public Person
{
    
    
protected:
	int _num = 0; //学号
};

class Teacher : virtual public Person
{
    
    
protected:
	int _id = 1; // 职工编号
};

class Assistant : public Student, public Teacher
{
    
    
protected:
	int _score = 60; // 主修课程
};
void Test()
{
    
    
	Assistant a;
	a._name = "peter";   //这个时候_name其实只有一个
	a.Student::_name = "xxx";  //这里的两个显示访问其实访问的都是一个变量
	a.Teacher::_name = "yyy";
}

6.4 Implementation principle of diamond virtual inheritance

PS: The following discussion is based on the above code. The program is a 32-bit program (just for convenience).
Insert image description here
However, student has an extra 0x00cf5dd8, and teacher has an extra 0x00cf5de4. These two variables are actually pointers, pointing to two tables. The offsets are recorded in the tables. Through this offset, the data from student (teacher) position starts to find _name. These two pointers are called virtual base table pointers, and these two tables are called virtual base tables .

Insert image description here


6.5 Summary of Diamond Virtual
  1. In practicetry to avoid writing diamond inheritance. When the object is complex and polymorphism is introduced, it will become quite complicated.
  2. Secondly, diamond inheritance will consume time, but the space consumption is not bad, becauseall subclass objects share the virtual base table.



7. Inheritance and composition

Inheritance relationship:

class Car {
    
    
protected:
	string _colour ; // 颜色
	string _num; // 车牌号
};

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

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

Combination relationship:

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

};

class Car {
    
    
protected:
	string _colour; // 颜色
	string _num; // 车牌号
	Tire _t; // 轮胎
};
  • Public inheritance is an is-a (B is A) relationship. That is to say, every derived class object is a base class object.
  • Combination is a has-a (B has A) relationship. Suppose B combines A, and there is an A object in every B object.
  • In practice the combination relationship is preferred over the inheritance relationship.
  • The inheritance relationship is a kind of "white box reuse". The internal details of the base class are visible to the derived class, which destroys encapsulation to a certain extent. The dependence between derived classes and base classes is very strong and the degree of coupling is high.
  • The combination relationship is a kind of "black box reuse". The internal details of the object are not visible, and only the corresponding interface is provided externally. There is no strong dependency between the combined classes, and the degree of coupling is low. Preferring object composition helps you keep each class encapsulated.
  • However, inheritance also has unique advantages. In some scenarios, inheritance is more semantic, and inheritance is necessary to achieve polymorphism.

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/133577104