[C + + advanced learning] polymorphism in C + +

Zero. Preface

C + + has five characteristics: object, encapsulation, inheritance, abstraction and polymorphism. The chapter will explain polymorphism in C + +

1, Concept and definition of polymorphism

  • Concept:

Generally speaking, it is a variety of forms. Specifically, it is to complete a certain behavior. When different objects complete it, they will produce different states

  • Example: buying a ticket

When ordinary people buy tickets, they buy tickets at full price; When students buy tickets, they buy tickets at half price; When soldiers buy tickets, they have priority

  • definition:

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

  • Polymorphic composition conditions:

    1. The virtual function must be called through a pointer or reference to the base class

    2. The called function must be a virtual function, and the derived class must override the virtual function of the base class

  • Diagram:

2, Virtual function

1. Concepts and definitions

  • Virtual function syntax:

Class member functions modified by virtual are called virtual functions

  • Example:
class Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "Buy a ticket-Full price" << endl; 
	}
};
  • Virtual function override:

There is a virtual function exactly the same as the base class in the derived class (that is, the return value type, function name and parameter list of the derived virtual function and the base virtual function are exactly the same). It is said that the virtual function of the subclass overrides the virtual function of the base class

  • Example:
class Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "Buy a ticket-Full price" << endl; 
	}
};
class Student : public Person {
public:
	virtual void BuyTicket()
	{ 
		cout << "Buy a ticket-50% Off" << endl;
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}
  • result:

2. Special case of virtual function rewriting

  1. covariant

A class virtual function returns a pointer or reference to a base class object. When a derived class virtual function returns a pointer or reference to a derived class object, it is called covariance (the return value type of the base class is different from that of the derived class virtual function)

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

void Func(Person& p)
{
	p.f();
}

int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}
  • result:
  1. Derived virtual functions without virtual

When overriding the virtual function of the base class, the virtual function of the derived class can also constitute an override without adding the virtual keyword

  • reason:

After inheritance, the virtual function of the base class is inherited (the virtual function property is still maintained in the derived class)

  • Example:
class Person {
public:
	virtual void BuyTicket()
	{
		cout << "Buy a ticket-Full price" << endl;
	}
};
class Student : public Person {
public:
	//Derived virtual functions can also form virtual functions without adding virtual
	void BuyTicket() 
	{ 
		cout << "Buy a ticket-50% Off" << endl; 
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}
  • result:
  • be careful:
  1. This kind of writing is not very standard and is not recommended

  2. If the virtual function of the base class does not add virtual and the virtual function of the derived class adds virtual, this situation does not constitute a virtual function

  1. Rewriting of destructor
  1. We know that base class pointers and references can point to base classes and derived objects. Therefore, when releasing objects through pointers and references, we need to realize the polymorphism of destructors, but the names of destructors of base classes and derived classes are different

  2. In order to solve this problem, the compiler makes special treatment on the name of the destructor, and the name of the destructor after compilation is uniformly processed into destructor

  • Example:
class Person {
public:
	virtual ~Person() 
	{ 
		cout << "~Person()" << endl; 
	}
};
class Student : public Person {
public:
	virtual ~Student() 
	{ 
		cout << "~Student()" << endl; 
	}
};
//Only the destructor of the derived class Student overrides the destructor of Person
//When the delete object calls the destructor, it can form polymorphism and ensure that the objects pointed to by p1 and p2 call the destructor correctly
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;
	return 0;
}
  • result:

3. C++11 override and final

  • introduce:
  1. C + + has strict requirements for function rewriting, but in some cases, due to negligence, it may cause the parent order of function names to be written, but it cannot form an overload, and this error will not be reported during compilation (the compiler will instantiate on demand, and only instantiation will be checked)

  2. For this purpose, C++11 provides two keywords: override and final, which can help users detect whether to rewrite

  1. final

Modifies a virtual function to indicate that the virtual function can no longer be overridden

  • Example:
class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-comfortable" << endl; }
};
  • result:
  1. 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

  • Example:
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-comfortable" << endl;}
};
  • result:

4. Overload / override / redefine comparison

  • Comparison diagram:

3, Abstract class

  • Concept:
  1. Write = 0 after the virtual function, then this function is a pure virtual function

  2. Classes containing pure virtual functions are called abstract classes (also known as interface classes). Abstract classes cannot instantiate objects

  3. A derived class cannot instantiate an object after inheritance. Only by overriding a pure virtual function can a derived class instantiate an object

  4. Pure virtual functions regulate that derived classes must be overridden. In addition, pure virtual functions embody interface inheritance

  • Example:
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();
}
  • result:
  • Interface inheritance and implementation inheritance:
  1. The inheritance of ordinary functions is a kind of implementation inheritance. Derived classes inherit the functions of the base class. Functions can be used. What they inherit is the implementation of functions

  2. The inheritance of virtual function is a kind of interface inheritance. The derived class inherits the interface of the virtual function of the base class. The purpose is to rewrite and achieve polymorphism. What is inherited is the interface (if polymorphism is not realized, do not define the function as a virtual function)

  • be careful:

Although function rewriting requires the same interface, the default values of parameters are not checked. If the default values of the base class virtual function and the derived class rewriting function are different, follow the interface of the base class virtual function, that is, ignore the interface of the derived class rewriting function

4, Principle of polymorphism

1. Virtual function table

  • Example:
// Here we often take a test question: how much is sizeof(Base)?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
  • result:
  • Explanation:

b object is 8bytes, except_ b members, one more_ vfptr is placed in front of the object

  • be careful:
  1. _ vfptr is generally stored in front of variables (the storage location is related to the platform)

  2. This pointer in the object is called the virtual function table pointer (v for virtual, f for function)

  3. A class containing virtual functions has at least one pointer to the virtual function table (because the address of the virtual function is put into the virtual function table, which is also called the virtual table)

  • Example:
// For the above code, we make the following modifications
// 1. We add a derived class Derive to inherit Base
// 2. Rewrite Func1 in derive
// 3.Base adds an imaginary function Func2 and an ordinary function Func3
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:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}
  • Diagram:
  • explain:
  1. d object consists of two parts. One part is the member inherited from the parent class (virtual table pointer), and the other part is its own member

  2. For the object of derived class d, because Func1 has been rewritten, the virtual table of d stores the rewritten derived:: Func1 (Rewriting the inherited virtual function and overwriting the corresponding virtual function address on the virtual function table is also called overwriting) (rewriting is the name of syntax and overwriting is the name of principle layer)

  3. After Func2 inherits, it is a virtual function, so it is put into the virtual table, and Func3 inherits, but it is not a virtual function, so it will not be put into the virtual table

  4. Virtual function table is essentially a pointer array containing virtual function pointers. Generally, a nullptr (used to indicate the end) is placed at the end of this array (depending on the compiler)

  • Virtual table generation summary of derived classes:
  1. First copy the contents of the virtual table in the base class to the virtual table of the derived class

  2. If the derived class overrides a virtual function in the base class, the virtual function of the base class in the virtual table is overwritten with the virtual function of the derived class itself

  3. The virtual function newly added by the derived class itself is added to the end of the virtual table of the derived class in the order of its declaration in the derived class

  • be careful:

The virtual table stores virtual function pointers, not virtual functions. Like ordinary functions, virtual functions have code segments, but the address of the function is stored in the virtual table. In addition, the virtual table pointer is stored in the object, and the virtual table exists in the code segment (on the vs platform) (the address of the object can be printed and compared with constants and variables)

2. Principle of polymorphism

  • Example:
class Person {
public:
	virtual void BuyTicket() { cout << "Buy a ticket-Full price" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "Buy a ticket-50% Off" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}
  • Diagram:
  • explain:
  1. p refers to when pointing to mike object, p - > buyticket. The virtual function found in mike's virtual table is Person::BuyTicket

  2. P refers to p - > buyticket when pointing to johnson object. The virtual function found in johson's virtual table is Student::BuyTicket

  3. In this way, different objects show different forms when they complete the same behavior

  4. To achieve polymorphism, there are two conditions: one is virtual function coverage, and the other is virtual function call by pointer or reference of object

  5. The function call after meeting polymorphism is not determined at compile time, but found in the object after running (so the condition of polymorphism includes using the pointer or reference of the base class object instead of the base class object); Function calls that do not satisfy polymorphism are confirmed at compile time

  • Diagram:
  • Compilation:
// The following assembly code that is not related to your problem has been removed
void Func(Person* p)
{
    ...
    p->BuyTicket();
    // Stored in P is the pointer of mike object. Move p to eax
    001940DE mov eax,dword ptr [p]
    // [eax] is the content pointed to by the eax value. Here, it is equivalent to moving the first four bytes (virtual table pointer) of the mike object to edx
    001940E1 mov edx,dword ptr [eax]
    // [edx] is the content pointed to by the edx value. Here, it is equivalent to moving the virtual function pointer stored in the first 4 bytes of the virtual table to eax
    00B823EE mov eax,dword ptr [edx]
    // Pointer of virtual function stored in call eax. It can be seen here that the call that meets polymorphism is not determined at compile time, but found in the object after running
    001940EA call eax
    001940EC cmp esi,esp
}
int main()
{
    ...
    // First of all, although BuyTicket is a virtual function, mike is an object and does not meet the condition of polymorphism. Therefore, when the call of an ordinary function is converted into an address, the address of the function has been confirmed from the symbol table during compilation, and the call address is directly used
    mike.BuyTicket();
    00195182 lea ecx,[mike]
    00195185 call Person::BuyTicket (01914F6h)
    ...
}

3. Dynamic binding and static binding

  • Concept:
  1. Static binding, also known as early binding, determines the behavior of the program during program compilation, which is 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 running of the program, which is also known as dynamic polymorphism

4. Multi inheritance virtual function table

Note: we want to see how the multi inherited virtual function table is, but there is something wrong with the content window (it can be considered that the compiler design did not take into account), so we can't see the actual situation. Therefore, here we design a special virtual function (return value, same parameters) and use the function of printing virtual function table to check the address and call verification

  • Example:
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) ();//The strong rotation of this function pointer is a special case
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;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
    //Take the address of the object (virtual function table pointer generally exists in the first four bytes of the object). If you want to take the address of the first four bytes, use int * to forcibly rotate (only forcibly rotate the pointer type can change the step size of the pointer)
    //Then dereference to get the virtual function table pointer. However, it is impossible to pass parameters (type mismatch) in this way. It should be forcibly converted to the virtual function pointer array type
	PrintVTable(vTableb1);
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));//First convert to char * step size to get the address of Base2 across Base1
	PrintVTable(vTableb2);
	return 0;
}
  • Diagram:
  • explain:

The UN overridden virtual functions of multiple inherited derived classes are placed in the virtual function table of the first inherited base class part

  • Why is the coverage address of func1 different for two virtual functions?

Each jump function is wrapped to the same address twice

  • For diamond Inheritance / diamond virtual inheritance:

In practice, it is not recommended to design diamond inheritance and diamond virtual inheritance: on the one hand, it is too complex and easy to cause problems. On the other hand, such a model has a certain performance loss when accessing base class members

  • Approximate result: example: B inherits A C inherits A D inherits B, C

5, Inheritance and polymorphism are common interview questions

  1. What is polymorphism?

In short, polymorphism refers to different states produced by the same behavior of different objects

That is, in class objects with different inheritance relationships, calling the same function produces different behaviors

  1. What are overloading, overriding (overwriting), redefining (hiding)?
  1. Overload: in the same scope, the same function name and different parameters constitute overload

  2. Rewriting: in the base class and inheritance class fields, for virtual functions, the function name, parameters and return value are the same (except for special cases), constituting rewriting

  3. Redefinition: in the base class and inheritance class fields, the function names are the same, which constitutes redefinition without overloading

  1. Implementation principle of polymorphism?
  1. For the virtual function class, the virtual function table pointer will be generated in the member variable of the object, and the virtual function address of the object is stored in the virtual function table pointed to

  2. The derived class will inherit the virtual function table of the base class. If the derived class overrides the virtual function, the corresponding function address in the inherited virtual function table will be overwritten into the virtual function of the derived class object

  3. When an object calls a virtual function, it will first find the virtual function table and find the corresponding virtual function through the virtual function table, thus forming a polymorphism

  1. Can an inline function be a virtual function?

Yes, but the compiler will ignore the inline attribute, so this function is no longer inline, because the virtual function needs to be placed in the virtual table

  1. Can static members be virtual functions?

No, because the static member function does not have this pointer, its virtual function table cannot be found through the object pointer. The calling method of type:: member function cannot access the virtual function table, so the static member function cannot be put into the virtual function table

  1. Can a constructor be a virtual function?

No, because the pointer of the virtual function table in the object is initialized at the constructor initialization list stage. If the constructor is a virtual function, the corresponding virtual function will be found in the virtual function in the object first, and the virtual function table is not generated at this time (dynamic polymorphism is runtime binding)

  1. Can a destructor be a virtual function? In what scenario is a destructor a virtual function?
  1. Yes, and it's best to define the destructor of the base class as a virtual function

  2. When the base class pointer points to the base class object from new or the derived class object from new, the pointer needs to be used to release the object. At this time, the destructor needs to be a virtual function to ensure the successful release of the object pointed to by the pointer

  1. Is it faster for objects to access ordinary functions or virtual functions?

If it's an ordinary object, it's as fast

If it is a pointer object or a reference object, the ordinary function called is fast. Because it constitutes polymorphism, it is necessary to look up the virtual function table when calling the virtual function at runtime

  1. At what stage is the virtual function table generated and where does it exist?

The virtual function table is generated in the compilation stage (during object construction). Generally, there are code segments (constant areas)

  1. C + + Diamond inheritance problem? The principle of virtual inheritance?
  1. Diamond inheritance has the problems of data redundancy and ambiguity

  2. Virtual inheritance will make the inherited parent class generate a virtual base table pointer in the member variable. Pointing to the virtual base table will store the distance between the inherited base class member variable and its member variable, and find its base class member variable through this distance, while the virtual base tables in the two inherited parent classes point to the parent class member variable of the same parent class

Note: do not confuse the virtual function table with the virtual base table

  1. What is an abstract class? The role of abstract classes?
  1. Abstract classes are used to represent some abstract things in reality. Abstract classes do not have instances. Derived classes only inherit their virtual function interfaces. It forces derived classes to rewrite virtual functions, otherwise derived classes are also abstract functions

  2. Abstract classes embody the interface inheritance relationship and are used to better represent some abstract things in reality

Keywords: C++ Polymorphism

Added by intergroove on Sun, 30 Jan 2022 17:40:18 +0200