Detailed Explanation of C++ Polymorphic 4D

After more than two months of preparation for the competition, I was quite satisfied with the final result of 5.21, and ended with a provincial three (algorithm).

During the period, I occasionally learn new knowledge every day, and then mainly do the questions, and the blog has fallen a lot, and now I will continue to make up (may be a little rusty).

Before learning c++ polymorphism, it is recommended to understand the inheritance of c++ first, at least to understand the basic concepts clearly, and then look at the chapter of polymorphism.

That's all for the gossip, now we are officially learning about polymorphism in C++.

Table of contents

1. What is polymorphism?

2. Definition and implementation of polymorphism

2.1, polymorphic composition conditions

2.2. Virtual functions

2.3. Rewriting

Definition and form of rewriting

The relationship between function rewriting/overriding, hiding and overloading

Two exceptions to virtual function overriding

2.4, C++11 override and final

3. Abstract class

4. The principle of polymorphism

4.1. Virtual function table

4.2, the principle of polymorphism

4.3, dynamic binding and static binding

5. Common interview questions about inheritance and polymorphism


1. What is polymorphism?

Polymorphism is also an important feature of c++ object-oriented, so what exactly is polymorphism?

Let's talk about the concept first:

The concept of polymorphism: Generally speaking, it is a variety of forms. The specific point is to complete a certain behavior. When different objects are completed, different states will be produced.

To give a simple example:

Let's go to buy train tickets. Different objects will have different results when buying tickets:

1. Ordinary people buy tickets: buy tickets at normal full price

2. Students buy tickets: buy tickets at half price

3. Tickets purchased by military personnel: Priority ticket purchase

There is also a red envelope code. Maybe newcomers will get more money if they scan it; if they scan it again, they may get less money.

Similar to these are concrete examples of polymorphism.

So how is polymorphism defined and how is it abstracted into code implementation?

2. Definition and implementation of polymorphism

2.1, polymorphic composition conditions

Polymorphism is when class objects with different inheritance relationships call the same function , resulting in different behaviors . For example, Student inherits from Person. The Person object buys the full price of the ticket, and the Student object buys the half price of the ticket.

Then there are two conditions to constitute polymorphism in inheritance :
1. The virtual function must be called through the pointer or reference of 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 write/overwrite.

A little more succinctly:

1. The parent class pointer or reference calls the virtual function

2. Virtual function rewriting

Don’t worry if you don’t understand this, we will analyze and explain the above conditions one by one.

Let's not talk about the first condition for now, let's look at the second condition first:

The virtual function is mentioned above , so what is a virtual function?

2.2. Virtual functions

We mentioned virtual functions in the previous chapter on diamond inheritance, and the keyword virtual.

Although the keyword of the virtual function in polymorphism is also virtual, the functions of the two are different.

The virtual in inheritance is to solve the problem , please refer to my previous chapter for details.

The virtual in polymorphism is used to modify the member functions in the parent class, so that the subclass can rewrite the parent class function (the virtual keyword is just a condition for rewriting the parent class function! There is another condition to be rewritten later).

Note here: the virtual function can only modify the internal member function of the class ! Static functions and constructors cannot use virtual functions.

So the virtual function is as follows:

class Person {
public:
    virtual void BuyTicket()
    {
        cout << "全价 - 买票" << endl;
    }
};

2.3. Rewriting

Definition and form of rewriting

Rewriting is also mentioned in the second condition above, and rewriting is also called coverage, so what is rewriting?

It is defined as follows:

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

To put it simply: virtual function (the overridden function in the parent class must be modified by virtual, the child function can be written or not, but it is best to write it) + three same (return value, function name, parameters are all the same) .

Take a look at the following example:

class Person {
public:
    virtual void BuyTicket()
    {
        cout << "全价 - 买票" << endl;
    }
};

//Student重写了Person中的BuyTicket的成员函数
class Student :public Person {
public:
    //满足父函数是个虚函数,返回值,函数名,参数名相同,构成重写
    virtual void BuyTicket()
    {
        cout << "半价 - 买票" << endl;
    }
};

The relationship between function rewriting/overriding, hiding and overloading

First, it is divided into two categories as a whole: 1. In the same scope 2. In different scopes

1. In the same scope:

This only involves function overloading, and meeting any one or two of the following conditions constitutes function overloading:

1. The number of parameters is different

2. Different parameter types or different order of parameter types

The return value can be the same or different, but at least one of the above two conditions must be met.

int Add(int x, int y)
{
    return x + y;
}
int Add(double x, double y)
{
    return x + y;
}
int Add(int x, int y, int z)
{
    return x + y + z;
}
int main()
{}

For example, the above functions constitute function overloading

2. In different scopes

At this time, it is necessary to distinguish between function rewriting/overriding and hiding.

Function rewriting We said above: the function that satisfies the virtual function + three same (return value, function name, parameter list) is function rewriting/overwriting.

As long as any one of the conditions is not satisfied, it constitutes function hiding.

For example, I made a random change in the above function rewriting code:

I added a parameter x to the subfunction

class Person {
public:
    virtual void BuyTicket()
    {
        cout << "全价 - 买票" << endl;
    }
};

class Student :public Person {
public:
    //加了一个参数x
    virtual void BuyTicket(int x)
    {
        cout << "半价 - 买票" << endl;
    }
};

At this time, their parameter lists are no longer the same, and they do not meet the conditions for rewriting, which constitutes function hiding.

 For another example, if we remove the virtual in the parent class, the condition of virtual function is no longer satisfied at this time, which also constitutes function hiding .

class Person {
public:
    //去掉了virtual,此时这个函数便不再是虚函数,不满足条件
    void BuyTicket()
    {
        cout << "全价 - 买票" << endl;
    }
};

class Student :public Person {
public:
    virtual void BuyTicket()
    {
        cout << "半价 - 买票" << endl;
    }
};

Therefore, it is necessary to constitute function rewriting/covering while satisfying the virtual function + three same.

As long as any one of them is not satisfied, it is function hiding.

Here is a diagram of the difference, you can take a look at it clearly:

 The second condition can now be clarified.

Next, explain the first condition: the virtual function must be called through the pointer or reference of the base class

What does it mean?

It will be easier to understand this with code:

(ps. If you feel that you can't read the code at this time, it is recommended to calm down and analyze it carefully, it is very simple code)

class Person {
public:
    virtual void BuyTicket()
    {
        cout << "全价 - 买票" << endl;
    }
};

class Student :public Person {
public:
    virtual void BuyTicket()
    {
        cout << "半价 - 买票" << endl;
    }
};

//基类是Person,所有调用必须通过基类指针或引用进行调用
void func(Person& p)
{
    p.BuyTicket();
}
int main()
{
    Person ps;
    Student st;
    
    //虽然是调用同一个函数,但是由于多态,结果会不一样
    func(ps);
    func(st);

    return 0;
}

We first create an object of the Person class and an object of the Student class.

Then call the func function separately, and then call the function through the reference of the base class.

So the results should be different, one is full price, one is half price, let's run the observation:

 It can be seen that the expected results have been achieved.

This is said to satisfy a condition of polymorphism.

But there are two exceptions : Failure to meet the conditions that constitute a rewrite still constitutes a rewrite:

Two exceptions to virtual function overriding

They are: 1. Covariance (the return value of the base class and the derived class are different, but the return value is required) 2. Rewriting of the destructor (the name of the destructor of the base class and the derived class is different)

Let’s look at the first one first: 1. Covariation

When the derived class overrides the virtual function of the base class, the return value type of the virtual function of the base class is different . But the return value must be a pointer or reference to the parent-child relationship.

Look at the code below:

class Person {
public:
    //返回值类型为Perosn*
    virtual Person* BuyTicket()
    {
        cout << "全价 - 买票" << endl;
        return this;
    }
};

class Student :public Person {
public:
    //返回值类型为Student*
    virtual Student* BuyTicket()
    {
        cout << "半价 - 买票" << endl;
        return this;
    }
};
void func(Person& p)
{
    p.BuyTicket();
}
int main()
{
    Person ps;
    Student st;

    func(ps);
    func(st);

    return 0;
}

When the subclass rewrites the parent class function, the return value is different, but at this time we execute the code:

The answer is still the same, which means it still constitutes polymorphism.

Of course, the parent-child relationship pointer is not necessarily the current parent class and subclass, but can also be the following:

class A {};
class B : public A {};
class Person {
public:
    //返回值类型为Perosn*
    virtual A* BuyTicket()
    {
        cout << "全价 - 买票" << endl;
        return nullptr;
    }
};

class Student :public Person {
public:
    //返回值类型为Student*
    virtual B* BuyTicket()
    {
        cout << "半价 - 买票" << endl;
        return nullptr;
    }
};

  

 This still constitutes polymorphism.

And the order cannot be reversed. For example, the return type of the Person class (parent) is not type B (child). It can only be A.

2. Rewriting of destructors (base class and derived class destructors have different names)

If the destructor of the base class is a virtual function, as long as the destructor of the derived class is defined at this time, no matter whether the virtual keyword is added, it will be rewritten with the destructor of the base class, although the name of the destructor of the base class and the derived class different. Although the function names are different, it seems that it violates the rewriting rules, but it is not. Here it can be understood that the compiler has made special treatment for the name of the destructor. After compilation, the name of the destructor is uniformly processed as destructor. So it also constitutes overridden.

This is the same as I explained in detail in the last chapter of C++ inheritance (destructors in derived classes).

2.4, C++11 override and final

It can be seen from the above that C++ has strict requirements on function rewriting, but in some cases, due to negligence, the alphabetical order of the function name may not constitute overloading, and this error will not be reported during compilation. , it is not worth the loss to debug only when the expected result is not obtained when the program is running, so: C++11 provides two keywords, override and final , which can help users detect whether to rewrite

1.final: Modifies the virtual function, indicating that the modified virtual function can no longer be rewritten

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

As above, Benz rewrites Drive() in the parent class Car. However, due to the final modification, Drive() in Car can no longer be rewritten:

 2.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 Car {
public:
    virtual void Drive() {}
};
class Benz :public Car {
public:
    virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

Then we change Drive to Direct.

 At this time, because the virtual function of the base class is not rewritten, the compiler detects it and reports an error due to the addition of override.

This is how the two keywords are used.

3. Abstract class

concept:

Add =0 after the virtual function, then this function is a virtual function. A class containing a pure virtual function is called an abstract class (interface class). An abstract class cannot instantiate an object. A derived class cannot instantiate an object after inheritance. Only by rewriting a pure virtual function can a derived class instantiate an object. The pure virtual function specifies that the derived class must be rewritten, and the pure virtual function also reflects the interface inheritance.

The general meaning is: if a class is an abstract class (containing pure virtual functions), then subclasses that inherit it must rewrite it to instantiate objects.

We can see the following example to understand it.

#include<iostream>
using namespace std;
class car{
public:
	virtual void Drive() = 0;
};
class Benz : public car{
	virtual void Drive()
	{
		cout << "Benz - 舒适" << endl;
	}
};
void Test()
{
	car* pBenz = new Benz;
	pBenz->Drive();
}
int main()
{
	Test();
	system("pause");
	return 0;
}

The above Benz class inherits and rewrites the Drive() in the parent class car. At this time, Benz can instantiate the object and call the function successfully.

(In class, written with the compiler on the school computer ----)

But if we no longer rewrite Drive() at this time, that is, directly inherit the pure virtual function of the parent class, then the subclass is equivalent to a pure virtual function and cannot be instantiated.

At this point, the object can no longer be instantiated.

This is the usage and function of pure virtual functions.

Interface Inheritance and Implementation Inheritance

The inheritance of ordinary functions is a kind of implementation inheritance. The derived class inherits the functions of the base class, can use the function, and inherits the implementation of the function. The inheritance of virtual functions 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, and what is inherited is the interface. So if you don't implement polymorphism, don't define functions as virtual functions.

My understanding of the above is:

Ordinary inheritance (implementation inheritance) is to inherit the method of the parent class function, so as to facilitate the use of the function implemented by the parent class.

And interface inheritance is to inherit the interface (function name or a certain attribute) of the parent class, and then rewrite it as its own unique method or attribute.

4. The principle of polymorphism

4.1. Virtual function table

First look at a topic to introduce a virtual function table:

What is the size of the following class?

//sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

By running the results, we find that the answer is 8 bytes.

Let's debug and see what variables are there:

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 pointer in the object is called the virtual function table pointer ( v stands for virtual, f stands for function). A class containing a virtual function has at least one virtual function table pointer , because the address of the virtual function must be placed in the virtual function table (_vfptr[0] is the address of Func1()), and the virtual function table is also referred to as virtual table, so if inherited, what is placed in this table in the derived class?

We make the following changes to the above code:

1. Add a derived class Derive to inherit the Base class

2. Rewrite Func1 in the derived class Derive.

3. Add a virtual function Func2 and a common function Func3 in Base.

The modified code is as follows:

//1.增加一个派生类Derive来继承Base类
//2.在派生类Derive中重写Func1.
//3.在Base中新增一个虚函数Func2和普通函数Func3.
class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
    //新增虚函数Func2
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
    //新增普通函数Func3()
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
//新增Derive类来继承Base类
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;
}

 At this point, let's analyze what is in the virtual function table of the parent class and the child class.

 Through observation and testing, we found the following problems:

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 a member inherited from the parent class , and the virtual
table pointer
also exists in this 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 Derive::Func1
is stored in the virtual table of d , so the rewriting of the virtual function Also called coverage, coverage refers to the coverage of virtual functions in the virtual table. Rewriting is called syntax, and coverage is called 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 a pointer array storing 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 Your own 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 (note here, the VS compiler monitoring window is If you can’t see it, you need to manually output and print it yourself, but there must be some).






 Here is another question that we are easily confused: where does the virtual function exist? Where does the virtual table exist?  Wrong answer: the virtual function exists in
the virtual table, and the virtual table exists in the object.

But many students feel that way. Note that
the virtual table stores pointers to virtual functions, 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
? In fact, if we verify it, we will find that there are code segments under vs , and the possible storage locations are different for different compilers.

4.2, the principle of polymorphism

The virtual function table was analyzed for a long time above, so what does this have to do with the principle of polymorphism?

Let's take the example mentioned above to illustrate, the example of buying a train ticket:

Remember that the Func function passes
Person::BuyTicket called by Person, and Student::BuyTicket is called by Student.

Take a look at the following code:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

1. When p is Mike, that is, an object of the Person class:

p->BuyTicket finds that the virtual function in mike's virtual table is Person::BuyTicket. 

2. When p is Johnson, that is, the Student class object

p->BuyTicket finds the virtual function Student::BuyTicket in johson's virtual table. 

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

4. Through the analysis of the following assembly code, it can be seen that the function call after satisfying the polymorphism is not determined at compile time, but is
found in the object after running. Confirmed at compile time when calling a function that does not satisfy polymorphism

First look at the source code:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person mike;
	Func(&mike);
	mike.BuyTicket();

	return 0;
}

We are divided into two parts: Func(&mike) and mike.BuyTicket(); these two statements.

First look at the disassembly of the Func(&mike) part:

 From the location of the call, it can be seen that it is running to determine the address, not the one that has been determined at compile time.

Let's look at the object and directly call mike.BuyTicket();:        

 First of all, although BuyTicket is a virtual function, mike is an object and does not meet the conditions 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 at compile time, and the address is called directly.

This is the difference between the two: one determines the address at runtime, and the other determines the address at compile time.

4.3, dynamic binding and static binding

 According to the above, we can divide into two different behaviors:

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, object calling member functions, etc.

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. The assembly code on 4.2 has explained well what is static (compile-time) binding and dynamic (run-time) binding.

As for the virtual function table in the single inheritance and multiple inheritance relationship, I have already explained the single inheritance above.

One thing to note in multiple inheritance is that the non-overridden virtual functions of the derived class in multiple inheritance are placed in the virtual function table of the first inherited base class .

5. Common interview questions about inheritance and polymorphism

1. What is polymorphism?

Answer: Polymorphism refers to class objects with different inheritance relationships calling the same function , resulting in different behaviors .

2. What is overloading, rewriting (covering), redefinition (hiding)?

answer:

Overloading: In the same scope , the function name is the same , but the number or order of parameters and parameter types are different.

Overriding (overriding): Under different scopes (usually inheritance relationships), the virtual function name, return value, and parameters of the subclass and the parent class must be exactly the same .

Redefinition (hidden): Under different scopes (generally inheritance relationships), the function names of the subclass and the parent class are the same , but the return value is the same and the parameters are the same (including order, type, number). any of the conditions.

In other words, under the premise of ensuring the same name (different scope), as long as the functions that do not meet the rewriting requirements are redefinition functions.

3. What is the implementation principle of polymorphism?

You can refer to 4. The realization principle of polymorphism in this article, which is roughly organized as follows:

Polymorphism in C++ is achieved through virtual functions and inheritance . Among them, the virtual function defined in the class will generate a virtual function table inside the object at runtime , and realize polymorphism through the table. Each class that contains virtual functions has a virtual function table. The virtual function table is an array of function pointers , which stores the virtual function pointers of this class , and these pointers point to the addresses of the virtual functions defined by this class.

When a class with virtual functions derives a new class, the new class will also have a virtual function table , which contains the virtual functions of the new class and the virtual functions inherited from the base class . If the subclass does not rewrite the virtual function of the base class, then its virtual function pointer will point to the virtual function address in the base class virtual function table ; if the subclass rewrites the base class virtual function, then its virtual function pointer will point to the subclass The address of the virtual function in the virtual function table.

When using a base class pointer or reference to call an overridden virtual function, the compiler will look up the correct virtual function address in the object's virtual function table. Each object will retain a pointer to its virtual function table. Since the base class pointer or reference can point to the derived class object, it can also call the virtual function of the same name implemented in the derived class, achieving the effect of polymorphism.

4. Can an inline function be a virtual function?

A: Yes , but inline is just a suggestion. When a function is a virtual function, because it needs to be placed in the virtual function table, inline will be .

The summary is: Yes, but writing is the same as not writing.

5. Can static members be virtual functions?

Answer: No, because static members do not have a this pointer, and the virtual function table cannot be accessed by calling the type::member function, so static members cannot be virtual functions.

 6. Can a constructor be a virtual function?

Answer: No, because the virtual function table pointer in the object is initialized at the stage of the initialization list of the constructor
.

Explain: the use of virtual functions is to achieve polymorphism, and to find virtual function calls in the virtual table at runtime.

The virtual table is initialized when the constructor initialization list is called.

At this time, the constructor is a virtual function. You go to the virtual table to find the constructor, but it has not been initialized, because it needs a constructor, so an error occurs, that is, the constructor cannot be initialized.

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

Answer: First of all, 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 it constitutes polymorphism , and calling a virtual function at runtime needs to look up in the virtual function table.

8. 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).

The virtual function table pointer is stored in the object.

9. What is an abstract class? The role of abstract classes?

A class containing pure virtual functions is called an abstract class, and you can refer to the content in the text for details.

The abstract class forcibly rewrites the virtual function, and the abstract class reflects the interface inheritance relationship.

At this point, C++ polymorphism is basically the same. If there is something you don’t understand or don’t understand, please feel free to private message or comment. But the best comment area, there are various articles pushed in private messages, too many messages may not be seen, posting in the comment area may also solve everyone's problems~

Guess you like

Origin blog.csdn.net/weixin_47257473/article/details/130872904