Understanding and recognition of C + + polymorphism

Posted by kubis on Thu, 27 Jan 2022 10:02:10 +0100

1. What is polymorphism?
Polymorphism refers to the various forms of function calls, which makes us call functions more flexible.
Polymorphism can be divided into static polymorphism and dynamic polymorphism
1) Static polymorphism: static polymorphism refers to the polymorphism at compile time, which is realized through function overloading. Find the function address according to the function naming rules, so as to call different methods.

2) Dynamic polymorphism (runtime): the parent class pointer or reference call overrides the virtual function
a. If the parent class pointer or reference points to the parent class, the virtual function of the parent class will be called. The corresponding virtual function in the subclass will be called according to which subclass it points to.

Conditions for dynamic polymorphism:
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.

When the conditions of polymorphism are met:
Related to the object, the virtual function of which object is called according to which object the pointer or reference of the parent class points to
Conditions not met:
It is related to the type. Whoever the type of call is, it will be called.

Definition of virtual function: only non static member functions of a class can be called virtual functions.
The virtual of a virtual function has nothing to do with the virtual of virtual inheritance.
Virtual function is to realize polymorphism, while virtual inheritance is to solve the data redundancy and ambiguity caused by diamond inheritance.

Rewriting of virtual functions:
If a derived class has a virtual function exactly the same as the base class (i.e. the return value type, function name and parameter list are exactly the same), it is said that the virtual function of the subclass overrides the virtual function of the base class.

Exceptions to overrides of virtual functions:
1) Covariance (the function name and parameters are the same, the return value is a pointer or reference, and the two return values must have a parent-child relationship)

The base class virtual function returns the pointer or reference of the base class object, and the derived class virtual function returns the pointer or reference of the derived class. Called covariance

class A{};
class B : public A {};
class Person {
public:
 virtual A* f() {return new A;}
};
class Student : public Person {
public:
 virtual B* f() {return new B;}
};

2)
The destructor is rewritten (the name of the destructor of the base class is different from that of the derived class), but the destructor is at the bottom

If the destructor of the base class is a virtual function, the destructor of the derived class will constitute an override with the destructor of the base class as long as it is defined, whether or not the virtual keyword is added

class Person {
public:
 virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
 virtual ~Student() { cout << "~Student()" << endl; }
};
// Only when the destructor of the derived class Student overrides the destructor of Person, the following delete object calls the destructor to 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;
}

Keyword final
1) Modifies a virtual function that cannot be overridden.
2) Modify a class, then the class becomes a leaf class and cannot be inherited.

class Car
{
public:
 virtual void Drive() final {}
};
class Benz :public Car
{
public:
 virtual void Drive() {cout << "Benz-comfortable" << endl;}
};

Keyword override: check whether the virtual function of the derived class overrides a virtual function of the base class. If it is not overridden, a compilation error will occur.

Comparison of overloading, overwriting (Rewriting), hiding (redefining)

abstract class
Classes containing pure virtual functions are called abstract classes
Pure virtual function: write = 0 after the virtual function, then this function is pure virtual function.

Characteristics of abstract classes
1. The object cannot be instantiated directly. The pure virtual function specifies that the derived class must be rewritten. (if it is not rewritten, the subclass is still an abstract class and the object cannot be instantiated). It better reflects the interface inheritance.

Interface inheritance and implementation inheritance:
The inheritance of ordinary functions is a kind of implementation inheritance. Derived classes inherit the functions of the base class. You can use the functions of the base class and inherit the implementation of functions.
The inheritance of virtual function is an interface inheritance. The derived class inherits the interface of the virtual function of the base class. The purpose is to rewrite and achieve polymorphism. Inherited is the interface.
If you do not implement polymorphism, do not define functions as virtual functions.

Principle of polymorphism:
1. Virtual function table

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

The reason is that there is a virtual table pointer at the beginning of the class.
Except_ b members, one more_ vfptr is placed in front of the object (some platforms may be placed in the back of the object, and under vs is placed in the front). This pointer in the object is called the virtual function table pointer.

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


The virtual table is generated in the compilation stage. Like ordinary functions, virtual functions are placed in the code segment (constant area). The virtual table stores the address of the virtual function.
The virtual table pointer initialization is generated in the initialization list stage.

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

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;
 VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
 PrintVTable(vTableb1);
 VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));
 PrintVTable(vTableb2);
 return 0;
}

Reflect on the two conditions for achieving polymorphism
1) Virtual function coverage - > in order to enable different objects to complete the same behavior, they show different forms.
2) Must be the pointer or reference of the base class object, call the virtual function - > parent class pointer or reference, and point to or reference the cut-out part of the parent and child class objects when slicing; If the function parameter is a parent object, the slice will only copy the member variable, not the virtual function table pointer vfptr. Therefore, it is unreasonable to copy it (the parent object should not have the vfptr of the child class)

Virtual tables are the same for objects of the same type, so they share the same virtual table.

Topics: C++ Back-end