Three major features of C++ object-oriented -- polymorphism (emphasis)

1. What is polymorphism?

Polymorphism means multiple forms, referring to the same thing, different people will have different effects; for example, you want to open a movie on Tencent Video, but this movie requires VIP privileges to watch, and You are not a VIP. When you click to watch the video, it detects that you are not a VIP member and it will not let you watch it. However, if someone else is a VIP account, click to watch the full version of the video. This is called polymorphism.

2. Definition and implementation of polymorphism

2.1 Virtual functions

Virtual function: That is, the class member function modified by virtual is called a virtual function.

2.2 Rewriting of virtual functions

Rewriting (covering) of virtual functions: There is a virtual function in the subclass that is exactly the same as the parent class (that is, the return value type, function name, and parameter list of the subclass virtual function are exactly the same as those of the parent class virtual function), called the subclass The virtual function of the parent class overrides the virtual function of the parent class.

2.3 Constituent conditions of polymorphism

Two conditions must be met to constitute polymorphism in the inheritance system:
1. The virtual function must be called by the pointer or reference of the parent class.
2. The called function must be a virtual function, and the subclass must rewrite the virtual function of the parent class.
Conditions for virtual function rewriting:
The conditions for virtual function rewriting are originally stipulated: virtual function + three same, but there are some exceptions.
1. The functions of the parent and child classes need to be virtual functions at the same time. In principle, the virtual keyword needs to be added. The virtual keyword must be added before the function of the parent class to be considered a virtual function, but the virtual function of the child class can not be virtual. , the reason is that the subclass inherits the member variables and member functions of the parent class, and the function of the parent class is a virtual function. It can be considered that this function of the subclass is also a virtual function, but it is recommended to add virtual to the functions of the parent and child classes.
2. In principle, three sameness must be satisfied: function name, parameters (exactly the same), return value must be the same; but there is an exception is covariance, covariance stipulates that the return value can be different, but it must be a pointer or reference of the parent-child relationship , and both must be pointers or references, one cannot return a pointer and the other a return reference.

1. Does not constitute polymorphism

//不构成多态
class Person
{
    
    
public:
	void func()
	{
    
    
		cout << "Person::func()" << endl;
	}
};
class Student: public Person
{
    
    
public:
	virtual void func()
	{
    
    
		cout << "Student::func()" << endl;
	}
};

int main()
{
    
    
	//父类的引用调用函数,父类的引用引用父类对象
	Person p;
	Person& rp = p;
	p.func();

	//父类的指针调用函数,父类的指针指向父类对象
	Person* pp = &p;
	pp->func();
	
	//父类的引用调用函数,父类的引用引用子类对象
	Student s;
	Person& rs = s;
	rs.func();

	//父类的指针调用函数,父类的指针指向子类对象
	Person* sp = &s;
	sp->func();

	return 0;
}

insert image description here

Second, the composition of polymorphism

//构成多态
class Person
{
    
    
public:
	virtual void func()
	{
    
    
		cout << "Person::func()" << endl;
	}
};
class Student: public Person
{
    
    
public:
	//虚函数的重写
	virtual void func()
	{
    
    
		cout << "Student::func()" << endl;
	}
};

int main()
{
    
    
	//父类的引用调用函数,父类的引用引用父类对象
	Person p;
	Person& rp = p;
	p.func();

	//父类的指针调用函数,父类的指针指向父类对象
	Person* pp = &p;
	pp->func();
	
	//父类的引用调用函数,父类的引用引用子类对象
	Student s;
	Person& rs = s;
	rs.func();

	//父类的指针调用函数,父类的指针指向子类对象
	Person* sp = &s;
	sp->func();

	return 0;
}

insert image description here

Special attention needs to be paid to the rewriting of the destructor (the function names of the destructor of the base class and the derived class are different).
If the destructor of the base class is a virtual function, the destructor of the derived class only needs to be defined at this time, regardless of whether the virtual key is added. Words, are rewritten with the destructor of the base class, although the names of the destructors of the base class and the derived class are different. Their function names are different, which seems to violate the rewriting rules. In fact, the compiler has made special treatment for the name of the destructor. After compilation, the name of the destructor is uniformly processed into destructor, so the destructor of the parent and child classes The function name of the function is the same, and it has no parameters and no return value, so it constitutes rewriting.
Does the destructor need to be virtual? need.
Why? The reasons are as follows:

If the destructor is not a virtual function, it does not constitute polymorphism:


class Person
{
    
    
public:
	//析构函数不构成多态
	/*virtual */~Person()
	{
    
    
		cout << "~Person()" << endl;
	}
};
class Student : public Person
{
    
    
public:
	~Student()
	{
    
    
		delete p;
		p = nullptr;
		cout << "~Student()" << endl;
	}

public:
	int* p = new int[100];
};

int main()
{
    
    
	Person* pp = new Person;
	//等价于pp->destructor(),不构成多态,
	//调用函数的时候按照pp的类型调用
	delete pp;

	Person* sp = new Student;
	//等价于sp->destructor(),不构成多态,
	//调用函数的时候按照sp的类型调用
	delete sp;

	return 0;
}

insert image description here

Destructors are virtual functions and constitute polymorphism:

class Person
{
    
    
public:
	//析构函数构成多态
	virtual ~Person()
	{
    
    
		cout << "~Person()" << endl;
	}
};
class Student : public Person
{
    
    
public:
	~Student()
	{
    
    
		delete p;
		p = nullptr;
		cout << "~Student()" << endl;
	}

public:
	int* p = new int[100];
};

int main()
{
    
    
	Person* pp = new Person;
	//等价于pp->destructor(),构成多态,
	//调用函数的时候按照pp指向的对象调用
	delete pp;

	Person* sp = new Student;
	//等价于sp->destructor(),构成多态,
	//调用函数的时候按照sp的指向的对象调用
	delete sp;

	return 0;
}

insert image description here
In this way, the destructor can be called correctly to release resources.

Summary: Polymorphism is the this of different objects passed to member functions, and different functions are called. If it is polymorphic, look at the object pointed to by the pointer or reference when calling the function. If it does not constitute polymorphism, only look at the type of the pointer or reference itself when calling the function.

2.4 override and final in C++11

It can be seen from the above that C++ has relatively strict requirements for function rewriting, but in some cases, due to negligence, it may cause a wrong function name or a wrong parameter return value to constitute rewriting, and this error is during compilation. It will not be reported, and it is not worth the loss to debug only when the expected result is not obtained when the program is running. Therefore: C++11 provides two keywords, override and final, which can help users detect whether to rewrite.

1. Override is used to modify the virtual function of the subclass. It is used to check whether the virtual function of the subclass has overridden a virtual function of the base class. If not, an error will be reported.
insert image description here
2. final is used to modify the virtual function, indicating that the virtual function can no longer be rewritten.
insert image description here

2.5 Comparison of rewriting (overriding), overloading, and redefining (hiding)

insert image description here

Three, the principle of polymorphism

3.1 Virtual function table

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
int main()
{
    
    
	cout << sizeof(Base) << endl;

	return 0;
}

The answer is 8, why? Obviously there is only one integer member variable, shouldn't it be 4? Because there are virtual functions in the Base class, a virtual function table pointer is added to the member variable, and the pointed virtual function table stores the addresses of all virtual functions.
insert image description here
Through observation and testing, we found that the b object is 8 bytes. In addition to the _b member, there is one more _vfptr placed in front of the object (note that some platforms may put it at the end of the object, this is related to the platform), this in the object The pointer is called the virtual function table pointer (v stands for virtual, f stands for function). A class containing virtual functions has at least one virtual function table pointer, because the address of the virtual function must be placed in the virtual function table, and the virtual function table is also referred to as the virtual table.

So what is stored in the virtual table of the subclass?

class Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
    
    
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
    
    
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
    
    
public:
	//子类重写Func1
	virtual void Func1()
	{
    
    
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
    
    
	Base b;
	Derive d;

	return 0;
}

insert image description here

  1. There is also a virtual table pointer in the derived class object d, and the d object is composed of two parts, one part is a member inherited from the parent class, and the other part is its own member.
  2. The virtual table of the base class b object and the derived class d object are different. Here we find that Func1 has been rewritten, so the rewritten Derive::Func1 is stored in the virtual table of d, so the rewriting of the virtual function is also called For coverage, coverage refers to the coverage of virtual functions in the virtual table. Rewriting is the name of the syntax layer, and coverage is the name of the principle layer.
  3. In addition, Func2 is inherited as a virtual function, so it is put into the virtual table, and Func3 is also inherited, but it is not a virtual function, so it will not be put into the virtual table.
  4. The essence of the virtual function table is an array of pointers that store virtual function pointers. Generally, a nullptr is placed at the end of this array.
  5. Summarize the virtual table generation of the derived class: a. First copy the content of the virtual table in the base class to the virtual table of the derived class b. If the derived class rewrites a virtual function in the base class, use the derived class's own The virtual function overrides the virtual function of the base class in the virtual table c. The newly added virtual function of the derived class is added to the end of the derived class virtual table according to the order of declaration in the derived class.

The virtual table stores virtual function pointers, not virtual functions. Virtual functions are the same as ordinary functions, and they all exist in the code segment, but their pointers are stored in the virtual table. In addition, what is stored in the object is not a virtual table, but a virtual table pointer. So where does the virtual table exist? Next, we can verify it through a piece of code: it
insert image description here
can be deduced from the above that the virtual table is stored in the constant area.

3.2 Revisiting polymorphic conditions

1. The pointer or reference of the parent class calls the virtual function
Question 1: Why can't the pointer or reference of the subclass call the virtual function?
Because the pointer or reference of the parent class can receive both the pointer or reference of the parent class and the pointer or reference (slice) of the subclass.
Question 2: Why can't the parent class object call the virtual function?
The explanation is as follows:
insert image description here
2. Rewriting of virtual functions
Why must subclasses rewrite virtual functions of parent classes to form polymorphism?
Because only after the rewriting is completed, the corresponding function address of the virtual function table of the subclass will be overwritten with the address of the virtual function after the rewriting of the subclass, and the corresponding virtual function can be called according to the object pointed to by the parent class pointer when calling. Pointing to the parent class object, go to the virtual function table of the parent class to find the virtual function pointer and call the function, and point to the subclass object, go to the subclass virtual function table to find the virtual function pointer and call the function, so that polymorphism can be completed.

insert image description here

3.3 Dynamic binding and static binding

  1. Static binding, also known as early binding (early binding), determines the behavior of the program during program compilation, also known as static polymorphism, such as: function overloading.
  2. Dynamic binding, also known as late binding (late binding), is to determine the specific behavior of the program according to the specific type obtained during the running of the program, and call specific functions, also known as dynamic polymorphism.

3.4 Virtual function tables of single inheritance and multiple inheritance relationships

3.4.1 Virtual function table in single inheritance

Print vtable:

class Base {
    
    
public:
	virtual void func1() {
    
     cout << "Base::func1" << endl; }
	virtual void func2() {
    
     cout << "Base::func2" << endl; }
private:
	int a;
};
class Derive :public Base {
    
    
public:
	virtual void func1() {
    
     cout << "Derive::func1" << endl; }
	virtual void func3() {
    
     cout << "Derive::func3" << endl; }
	virtual void func4() {
    
     cout << "Derive::func4" << endl; }
private:
	int b;
};

//函数指针
typedef void (*FUNC_PTR) ();

void PrintVFT(FUNC_PTR table[])
{
    
    
	//虚表是以nullptr为结束标志的,所以
	// 可以借助这个来当循环的结束条件
	for (int i = 0; table[i] != nullptr; i++)
	{
    
    
		printf("[%d]->%p\n", i, table[i]);
	}
	printf("\n");
}

int main()
{
    
    
	Base b;
	Derive d;
	int ptr = *(int*)&d;
	PrintVFT((FUNC_PTR*)ptr);

	return 0;
}

insert image description here
insert image description here
insert image description here

3.4.2 Virtual function table in multiple inheritance

Print the virtual function table:

class Base1 {
    
    
public:
	virtual void func1() {
    
     cout << "Base1::func1" << endl; }
	virtual void func2() {
    
     cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 {
    
    
public:
	virtual void func1() {
    
     cout << "Base2::func1" << endl; }
	virtual void func2() {
    
     cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
    
    
public:
	virtual void func1() {
    
     cout << "Derive::func1" << endl; }
	virtual void func3() {
    
     cout << "Derive::func3" << endl; }
private:
	int d1;
};

函数指针
typedef void (*FUNC_PTR) ();

void PrintVFT(FUNC_PTR table[])
{
    
    
	//虚表是以nullptr为结束标志的,所以
	// 可以借助这个来当循环的结束条件
	for (int i = 0; table[i] != nullptr; i++)
	{
    
    
		printf("[%d]:%p->", i, table[i]);
		FUNC_PTR func = table[i];
		func();
	}
	printf("\n");
}

int main()
{
    
    
	Derive d;
	cout << sizeof(d) << endl;

	int vft1 = *((int*)&d);
	Base2* ptr = &d;
	int vft2 = *((int*)ptr);

	PrintVFT((FUNC_PTR*)vft1);
	PrintVFT((FUNC_PTR*)vft2);

	return 0;
}

Observing the figure below, it can be seen that the non-overridden virtual functions of the multi-inheritance derived class are placed in the virtual function table of the first inherited base class part.
insert image description here

insert image description here
insert image description here
insert image description here

4. Practice

first question,
insert image description here

second question,
insert image description here

The third question,
insert image description here
the fourth question,
insert image description here

5. Consider the following questions

  1. What is polymorphism? Answer: Static polymorphism (function overloading), dynamic polymorphism (rewriting of virtual functions in inheritance + calling of parent class pointers). It is more convenient and flexible to call in various forms.
  2. What is overloading, rewriting (covering), redefinition (hiding)? Answer: Refer to the content of the article.
  3. The realization principle of polymorphism? Answer: Static polymorphism (function name modification rules), dynamic polymorphism (virtual function table).
  4. Can an inline function be a virtual function? Answer: Yes, but the compiler will automatically ignore the inline attribute, and this function is no longer an inline function, because the virtual function must be placed in the virtual table, and the inline function is expanded at compile time, and there is no function address. Therefore, if the virtual function address is to be placed in the virtual table, there must be a function address.
  5. Can a static member be a virtual function? Answer: No, because the static member function does not have a this pointer, and the virtual function table cannot be accessed by calling the type:: member function, so the static member function cannot be placed in the virtual function table.
  6. Can a constructor be virtual? Answer: No, because the virtual function table pointer in the object is initialized at the constructor initialization list stage.
  7. Can a destructor be virtual? Under what circumstances is the destructor a virtual function? Answer: Yes, and it is best to define the destructor of the base class as a virtual function. Refer to the content of the article.
  8. Is object access to ordinary functions faster or virtual functions faster? Answer: If it is an ordinary object, it is as fast. If it is a pointer object or a reference object, the ordinary function called is fast, because once the polymorphism is formed, the virtual function needs to be searched in the virtual function table when it is called at runtime.
  9. At what stage is the virtual function table generated and where does it exist? Answer: The virtual function table is generated during the compilation stage, and generally exists in the code segment (constant area). (Refer to the content of the article verification.)
  10. Problems with C++ diamond inheritance? The principle of virtual inheritance? Answer: Data redundancy and ambiguity (refer to the previous article on inheritance). Be careful not to confuse the virtual function table with the virtual base table.

The above is what I want to share with you today. Have you learned it? Polymorphism is a very important content, and it is often used in practice, so you must master it. If you feel that it is helpful, please like it, click it, and follow it. We will continue to update C++ related knowledge in the future. We See you next time! ! ! ! ! ! ! ! ! ! ! ! !

Guess you like

Origin blog.csdn.net/weixin_70056514/article/details/131901298