Section 7 of C + + after the initial stage - polymorphism

Posted by futurshox on Sun, 27 Feb 2022 04:05:10 +0100

Our task in this section is to find out the polymorphism

catalogue

1, Concept of polymorphism

2, Override (overwrite) of virtual function:

3, override and final in C++11

Keyword override:

Keyword: final

Comparison: overload, overwrite (override), hide (redefine)

4, Abstract class

concept

Interface inheritance and implementation inheritance

5, Principle of polymorphism

Virtual function table:

The principle of realizing polymorphism:

Virtual function table of single inheritance and multi inheritance relationship

Statement:

All the following examples are implemented in the program under the x86 32-bit machine platform. If it is on a 64 bit platform, it is necessary to consider the problem that the pointer is 8 bytes.

1, Concept of polymorphism

Generally speaking, polymorphism is a variety of forms.

The specific point is to complete a certain behavior. When different objects complete it, they will produce different states.

In general, we can classify it as follows:

1. Static polymorphism.

2. Dynamic polymorphism.

Static polymorphism is actually very simple, that is, function overloading. We will not repeat too much here.

Dynamic polymorphism is also called runtime polymorphism.

For dynamic polymorphism, at least two conditions must be met:

① Subclasses inherit the parent class and complete the rewriting of virtual functions.

② The pointer or reference of the parent class to call the overridden virtual function.

The result is:

If the pointer or reference of the parent class points to the parent object, the virtual function of the parent class is called.

If the pointer or reference of the parent class points to the subclass object, it calls the virtual function of the subclass.

In other words, the calling function is related to the object. The virtual function will be called according to the object pointed to.

For example, when drawing red envelopes, everyone is a different individual, and the amount everyone draws is different

As for what virtual function is and what the above words mean, we will introduce it in detail. After the introduction, you will naturally understand it.

First, let's talk about what is called virtual function.

Very simply, class member functions modified by virtual are called virtual functions.

Note that its modification must and can only be member functions! You can't add virtual to a global function casually.

2, Override (overwrite) of virtual function:

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),

The virtual function of the subclass overrides the virtual function of the base class.

And the keyword virtual should be added in front of the function

for instance:

#include<iostream>
using namespace std;
class Person
{
public:
	virtual void func()
	{
		cout << "i am a person" << endl;
	}
};
class Student : public Person
{
public:
	virtual void func()                         
	{
		cout << "i am a Student" << endl;
	}
};
class Slodier : public Person
{
public:
	virtual void func()         //It must be a virtual function decorated with virtual, and it must be rewritten
	{
		cout << "i am a Slodier" << endl;
	}
};

void func(Person& a)     
{
	a.func();
}

int main()
{
	Person pp;
	Student ss;
	pp.func();
	ss.func();
	return 0;
}

Similarly, if I instantiate the class of Slodier and call func function, I will get "i am a Slodier"

It should be noted that the functions of the derived class and the base class mentioned above are "exactly the same". In fact, there are two special cases. In other words, there are two exceptions:

① One is covariance:

That is, the return value types of virtual functions of base class and derived class are different

When a derived class overrides a base class virtual function, the return value type is different from that of the base class virtual function.

That is, when 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 object,

Called covariance.

Similarly, let's take an example:

class A {};
class B : public A {};
class Person {
public:
	virtual A* f() { return new A; }  //The return value is A *, and its return type is the base class object
};
class Student : public Person {
public:
	virtual B* f() { return new B; }  //The return value is B *, and its return type is derived class object
};

② The other is the rewriting of the destructor (that is, the name of the destructor of the base class is different from that of the derived class)

Another example:

#include<iostream>
using namespace std;
class Person
{
public:
	virtual void func()
	{
		cout << "i am a person" << endl;
	}
	virtual ~Person()
	{
		cout << "~Person()" << endl;
	}
};
class Student : public Person
{
public:
	virtual void func()                         
	{
		cout << "i am a Student" << endl;
	}
	virtual ~Student()
	{
		cout << "~Student()" << endl;
	}
};
class Slodier : public Person
{
public:
	virtual void func()         //It must be a virtual function decorated with virtual, and it must be rewritten
	{
		cout << "i am a Slodier" << endl;
	}
	virtual ~Slodier()
	{
		cout << "~Slodier()" << endl;
	}
};

void func(Person& a)     
{
	a.func();
}

int main()
{
	Person* pp = new Person;
	Student* ss = new Student;
	Slodier* s = new Slodier;
	delete pp;
	delete ss;
	delete s;
	return 0;
}

As shown in the above code, all destructors here constitute rewriting.

Why is this possible? We can understand it this way: in order to better meet the first in and last out stack access principle when decomposing classes, the compiler will unify all destructors into destructor functions after compilation.

Here, we would like to emphasize:

In the destructor, if there is an inheritance relationship like the above, it is best to add virtual in front to make all destructors polymorphic.

Because if you don't, the destructor in the derived class may not be able to complete the call after slicing. It may cause memory leakage.

Another point is that virtual can only be added in front of the destructor of the base class once. The compiler inherits the destructor by default. Even if virtual is not added to the destructor of the derived class, it can still constitute an override with the destructor of the base class.

3, override and final in C++11

C + + has strict requirements for function rewriting, but in some cases, due to negligence, the alphabetical order of function names may be caused
Instead, writing cannot constitute overloading, and this error will not be reported during compilation. Debugging will not pay off until the program does not get the expected results when running. Therefore, C++11 provides two keywords: override and final, which can help users detect whether to rewrite.

Keyword 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.

Take a simple example:

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

As shown above, an override is added at the end of the function.

If there is no rewriting with virtual, the compiler will make a mistake. You can try.

Keyword: final

It modifies the virtual function, indicating that the virtual function can no longer be inherited

 

Now, let's:

Comparison: overload, overwrite (override), hide (redefine)

Overloading: two functions are in the same scope. They have the same function and parameter names. (parameter types are not identical)

Override: two functions are in the scope of base class and derived class respectively; Function name, return value and parameters must be the same (except covariance); Both functions must be virtual.

Redefine (hide): as long as the function name is the same and the two functions are in the base class and derived class respectively, they do not constitute an override, then it is redefined (hide).

4, Abstract class

concept

Write = 0 after the virtual function, then this function is a pure virtual function.

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

A derived class cannot instantiate an object after inheritance,

Only by overriding pure virtual functions can derived classes instantiate objects.

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

To say human words is that you have to rewrite me! Otherwise you won't be allowed to use (instantiate)

Take an example of a pure virtual function (abstract class)

class Car
{
public:
    virtual void Drive() = 0;    //The pure virtual function here makes this class an abstract class,
                                 //The derived class must override this function before it can be instantiated
};
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();
}
int main()
{
    Test();
    return 0;
}

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. Functions can be used. What they inherit is the implementation of functions

The inheritance of virtual function is a kind of interface inheritance. The derived class inherits the interface of the base class virtual function. The purpose is to rewrite and achieve polymorphism. What is inherited is the interface. So if you don't implement polymorphism, don't define functions as virtual functions.

Now, the focus is officially started. If you are still confused about what is said above, the next knowledge should make you suddenly enlightened.

5, Principle of polymorphism

Virtual function table:

Let's look at this question first:

Just want to ask: how much is sizeof(Car)?

class Car
{
public:
	virtual void Drive() {}
private:
	int _a;
};
//Seek siezof (car)?

Let's take a look at the answer first:

(again, the environment here is x86, not x64. The data on 64 bit platforms may be different from here, because both int and pointer are 8 bytes, but the principle is the same)

Why?

Why is the answer 8?

Let's open our monitoring window to see:

We can see that there are two things in c it.

Among them, it is called_ vfptr is a pointer, a secondary pointer.

More precisely, it is the address of the first element of a pointer array. The virtual function table is an array of pointers, which ends with nullptr.

Here_ vfptr points to the virtual table of the virtual function. (v can be understood as virtual and f as function)

In the virtual table, the address of the virtual function is stored.

Drawing is understood as:

 

Let's take another look at the following code:

We will find that there are pointers to two functions in the virtual function table in b, that is, the function with virtual.

In the virtual function table in d, there is only one pointer to the virtual function.

When inheriting, the process is like this:

Inherit the virtual table first. If the virtual function is rewritten, modify the address in the virtual table to the address of the new function.

In the derived class here, we only see the address of one function, which may be due to the optimization of our compiler. If we want to print it out, we can also do it:

We can see that as long as it is decorated with virtual, there will be its own location in the virtual table.

We rewrite Func1, but Func2 is decorated with virtual, but it is not rewritten. After inheritance, the address of this position in the virtual table of the derived class does not change (we can't see it after compiler optimization). Only when overridden, the address corresponding to the derived class will change accordingly.

Combined with the above examples, let's summarize a little. About the virtual function table:

1. There is also a virtual table pointer in the derived class object d. the d object is composed of two parts. One part is the member inherited from the parent class, and the virtual table pointer is the existing part, and the other part is its own member.

2. The virtual table of the base class b object is different from that of the derived class d object. Here, we find that Func1 has been rewritten, so the rewritten derived:: Func1 is stored in the virtual table of d, so the rewriting of virtual functions is also called overwriting, which refers to the overwriting of virtual functions in the virtual table.

Rewriting is the name of grammar, and covering is the name of principle layer.

3. In addition, Func2 inherits from the virtual function, so it is put into the virtual table, and Func3 inherits from it, 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 with virtual function pointers, and a nullptr is placed at the end of this array.

5. Summarize the virtual table generation of derived classes:

a. First copy the contents of the virtual table in the base class to the virtual table of the derived class

b. 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

c. 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.

6. Here is another easily confused question: where do virtual functions exist? Where does the virtual table exist?

Answer: virtual functions exist in virtual tables, and virtual tables exist in objects. Note that the answer above is wrong.

Note that 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. In addition, the object is not a virtual table, but a virtual table pointer.

So where does the virtual table exist? In fact, when we verify it, we will find that there are code segments under vs.
 

The principle of realizing polymorphism:

We emphasized the following sentence before:

The overridden virtual function must be called with a pointer or reference to the parent class. Cannot be called directly with an object.

Why?

Let's look at:

Both Person and Student have their own virtual tables.

When we call with reference or pointer, after slicing, the remaining virtual function table is our own virtual table.

When we call with an object, the virtual table of the passed object is the virtual table of the parent class.

Like this (as shown in the following figure:)

If you want to explain, we can understand it by drawing:

(whoever passes it, who uses it)

 

From the perspective of compilation:

Virtual function call (polymorphic call) to complete Rewriting:

The following is the call of ordinary functions:

 

Therefore, we can see that the virtual function finds the address of p after the program runs.

The ordinary function has determined the address of the function when compiling, and then directly call it.

This also proves that using virtual functions is dynamic polymorphism.

Dynamic binding and static 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 according to the specific type and call specific functions during the running of the program, also known as dynamic polymorphism

Virtual function table of single inheritance and multi inheritance relationship

As for single inheritance, we have explained it in detail above. In a word, copy it first and overwrite it after rewriting.

Now let's focus on multi inheritance, especially the problem of polymorphism left in the virtual base table of diamond inheritance in the previous section.

First of all, for ordinary multi inheritance, if you inherit multiple classes, there will be multiple virtual tables: (as shown below)

The complete code of our figure above is as follows:

#include<bits/stdc++. h> / / this is a universal header file. You need to configure it before you can use it
using namespace std;
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(*Fun)();
void pr(Fun* ptr)
{
	for (int i = 0; ptr[i] != nullptr; i++)
	{
		printf("%d point:[%p]\n", i, ptr[i]);
	}
}
int main()
{
	Derive d;

	return 0;
}

Then, the problems in the virtual table in diamond inheritance mentioned last time are as follows:

This picture is what we said in the previous section.

code:

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
	virtual void func()
	{
		cout << "A func()" << endl;
	}
	int _a;
};
class B : virtual public A
{
public:
	virtual void func()
	{
		cout << "B func()" << endl;
	}
	int _b;
};
class C :virtual public A
{
public:
	virtual void func()
	{
		cout << "C func()" << endl;
	}
	int _c;
};
class D :public B, public C
{
public:
	virtual void func()
	{
		cout << "D func()" << endl;
	}
	int _d;
};

int main()
{

	D d;
	d._a = 1;
	d._b = 2;
	d._c = 3;
	d._d = 4;
	return 0;
}

However, the virtual functions we rewrite here are also available in the parent class, that is, the virtual table of A can be the same as that of B and C.

What if B and C have their own virtual functions?

Change it slightly to the following:

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
	virtual void func()
	{
		cout << "A func()" << endl;
	}
	int _a;
};
class B : virtual public A
{
public:
	virtual void func()
	{
		cout << "B func()" << endl;
	}
	virtual void func1()   //New join
	{

	}
	int _b;
};
class C :virtual public A
{
public:
	virtual void func()
	{
		cout << "C func()" << endl;
	}
	int _c;
};
class D :public B, public C
{
public:
	virtual void func()
	{
		cout << "D func()" << endl;
	}
	virtual void func1()   //New join
	{
		cout << endl;
	}
	int _d;
};

int main()
{

	D d;
	d._a = 1;
	d._b = 2;
	d._c = 3;
	d._d = 4;
	return 0;
}

(as shown above)

There is another item stored in B - its own virtual table pointer. In its virtual base table, FC FF is the value of its offset. (that is, the value of the distance from the virtual table pointer)

Let's summarize our knowledge about polymorphism: (actually, I'll repeat it again)

 

Well, let's introduce the knowledge of polymorphism here first~~~~

Welcome to the author 😀

Topics: C C++ data structure Back-end