[C + +] detailed explanation of polymorphism

concept

Polymorphism, that is, multiple forms, that is, different objects will produce different states when they complete a certain behavior.
For example, when buying tickets in the past, ordinary people bought tickets normally, students bought tickets at half price, and soldiers bought tickets first.

Definition and Implementation

Composition conditions of polymorphism

Polymorphism is that class objects with different inheritance relationships call the same function and produce different behaviors.

There are two conditions for polymorphism in inheritance:
·A virtual function must be called through a pointer or reference to the base class.
·The called function must be a virtual function, and the derived class needs to override the virtual function of the base class.

class Person
{
public:
	virtual void func()
	{
		cout << "average person->Normal ticket buying" << endl;
	}
};

class Student : public Person
{
public:
	//The subclass must override the virtual function of the parent class
	virtual void func()
	{
		cout << "student->Half price ticket" << endl;
	}
};
//Must be a pointer or reference to the parent class to call a virtual function
//The parameter type here cannot be an object, otherwise it is a temporary copy and cannot form polymorphism
void F(Person& ps)
{
	ps.func();
}

int main()
{
	Person ps;
	Student st;
	F(ps);
	F(st);
	return 0;
}

Virtual function and its rewriting

Virtual function, that is, the class member function rewritten by the virtual keyword.
Override (override): if there is a virtual function in the derived class that is exactly the same as that in the base class (three same: the return value, function name and parameter type are the same), it is said that the subclass overrides the virtual function of the parent class.
[note] to form polymorphism, the parent function must be decorated with the virtual keyword, while the child class can omit the virtual keyword because it inherits the parent class, but this writing method is not very standardized and is not recommended.

Two exceptions to virtual function rewriting conditions

1. Covariance
The return value of the overridden virtual function can be different, but the return value must be the pointer or reference type of the parent and child classes.

class A {};
class B :public A {};
class Person
{
public:
	virtual A* func()
	{
		cout << "virtual A* func()" << endl;
		return new A;
	}
};
class Student : public Person
{
public:
	virtual B* func()
	{
		cout << "virtual B* func()" << endl;
		return new B;
	}
};

2. Rewriting of destructor
As we said before, the compiler will automatically process the name of the destructor as destructor. The destructor in the hidden inheritance relationship needs to form a rewriting relationship, for example:

class Person
{
public:
	virtual ~Person()
	{
		//Data cleaning
	}
};
class Student : public Person
{
public:
	virtual ~Student()
	{
		//Data cleaning
	}
};
int main()
{
	Person* pps = new Person;
	Person* pst = new Student;
	//Here, only the subclass overrides the virtual function of the parent class,
	//In order to ensure that the objects pointed to by pps and pst can call the destructor correctly
	delete pps;
	delete pst;
	return 0;
}

C++11 keyword override and final

·final: modifies the virtual function, indicating that the virtual function cannot be overridden

//If final modifies A class, class A cannot be inherited
class A final
{
public:
	//virtual void func() final {}
};

//class B : public A
class B
{
public:
	//final modifies the virtual function, then the virtual function func cannot be overridden
	//virtual void func() {}
};

·override: check whether the virtual function of the derived class overrides a virtual function of the base class. If not, compile and report an error.

class A
{
public:
	virtual void func() {}
};

class B : public A
{
public:
	//If it is not rewritten, an error is reported
	virtual void func() override {};
};

Comparison of overloading, redefining (hiding) and rewriting (overwriting)

abstract class

concept

Adding = 0 after the virtual function is the pure virtual function, and the class containing the pure virtual function is the abstract class (interface class). An abstract class cannot instantiate an object. If a derived class inherits an abstract class and does not override a pure virtual function, it is still an abstract class and cannot instantiate an object.
Pure virtual functions regulate that derived classes must override virtual functions, and more reflect interface inheritance.

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-comfortable" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-Manipulation" << endl;
	}
};
void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW; 
	pBMW->Drive();
}

Significance of abstract class: some objects directly instantiate objects, which are generally meaningless. For example, Car generally does not instantiate objects directly, but instantiates objects specific to a Car brand. At this time, Car can be designed as an abstract class.

Interface inheritance and implementation inheritance

The inheritance of ordinary functions is interface inheritance. Derived classes can use the functions of the base class; The rewriting of virtual function is to realize inheritance. The derived class inherits only the function interface of the base class. The purpose is to rewrite the function body of the virtual function of the base class and achieve polymorphism. Therefore, if polymorphism is not implemented, do not define functions as virtual functions.

principle

Virtual function table

In fact, for classes that define virtual functions, there is a hidden pointer to a virtual function table, which stores the address of the virtual function:

class A
{
public:
	virtual void func() { cout << "A :: func() " << endl; }
protected:
	int _a;
	char _c;
};
class B : public A
{
public:
	virtual void func() { cout << "B :: func() " << endl; }
};
int main()
{
	A aa1;
	A aa2;
	B bb;
	cout << sizeof(aa1) << endl;
	return 0;
}

The running result of the above code is 12. By calling the monitoring window, you can see that there is an additional pointer in the aa object, which points to a function pointer array, that is, the virtual function table. This is why the size of aa is 12 bytes.

And you can see:
·All objects of a class share a virtual table
·Whether parent and child classes rewrite virtual functions or not, the virtual function tables are independent.
Secondly, it should be noted that only virtual functions will be placed in the virtual table, and ordinary functions will not be placed in the virtual table; The virtual table is an array of function pointers, and a nullptr is usually placed at the end,

Underlying principle

So, after completing the above analysis, what is the principle of polymorphic implementation?
In fact, polymorphic calls to virtual functions are realized through virtual table pointers.

Static binding and dynamic binding

In fact, the construction of virtual table is realized when the object instantiates and calls the initialization list of constructor. This is a kind of dynamic binding, also known as late binding (late binding).

  1. Static binding, also known as 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 and call specific functions according to the specific types during the operation of the program, which is also known as dynamic polymorphism.

Summary

·Virtual table generation of derived classes:
1. The derived class will first copy the virtual table of the base class to its own virtual table
2. If the derived class overrides the virtual function of the base class, the virtual function of the derived class will override the virtual function of the base class in the virtual table
3. The virtual function newly added by the derived class itself is added to the end of the virtual table of the derived class according to the declaration order in the derived class
·The virtual table stores virtual function pointers, not virtual functions
·Virtual functions, like ordinary functions, have code segments, but their pointers are stored in the virtual table
·Object is not a virtual table, but a virtual table pointer.

Virtual function tables in single inheritance and multi inheritance relationships

Virtual function table in single inheritance

Let's start with the following code:

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;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}


Through the monitoring window, we can find that the virtual table of d object in the monitor lacks the addresses of two virtual functions. In fact, it can be considered as a small bug of vs compiler. Then we can print these two hidden virtual functions through the function.
The idea is that the virtual function table pointer is stored in the first four bytes of the class in the vs environment, and the virtual table ends with nullptr. Then we can call the virtual function through the virtual table pointer.

typedef void(*VFPTR)();

void PrintVirtualTable(VFPTR vTable[])
{
	cout << "Virtual table address:" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; i++)
	{
		printf("The first%d Virtual function address:%p\n", i, vTable[i]);
		VFPTR pf = vTable[i];
		pf();
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;
	PrintVirtualTable((VFPTR*)*((int*)(&b)));
	PrintVirtualTable((VFPTR*)*((int*)(&d)));
	return 0;
}

The idea here is:
1. Take out the d address and forcibly convert it to int type
2. In dereference, this is the first four bytes in d, that is, the contents of the virtual table pointer
3. Forcibly convert this int pointer to VFPTR * type, because the virtual table is an array storing VFPTR type pointers.
4. Pass the virtual table pointer to PrintVTable to print the virtual table.
5. It should be noted that the code for printing the virtual table often crashes, because the compiler sometimes does not deal with the virtual table cleanly, and there is nothing at the end of the virtual table
Put nullptr, resulting in out of bounds. This is a compiler problem. We just need to click the - generate - clean solution in the directory bar and compile it again.

In this way, we can see the func3 and func4 functions hidden by the compiler monitoring window.

Virtual function table in multi inheritance

In multi inheritance, if the derived class has its own non overridden virtual function, where will the virtual function be placed?

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(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	cout << " Virtual table address>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" The first%d Virtual function address :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Derive d;
	//Virtual functions in Base1
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);
	//Virtual functions in Base2
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);
	return 0;
}


You can see that the UN overridden virtual functions of multiple inherited derived classes are placed in the virtual function table of the first inherited base class

Keywords: C++ Back-end

Added by xkaix on Sat, 26 Feb 2022 19:04:25 +0200