Object Oriented Features – Polymorphism

ps: Warm reminder, since many knowledge points of polymorphism are based on inheritance, it is recommended that you review or study inheritance first.
You can refer to this blogger's article:
π1. The concept of polymorphism
β The concept of polymorphism: Generally speaking, it is a variety of forms. The specific point is to complete a certain behavior. When different objects complete it, different states will be generated.
β To buy tickets at the train station, there are students, ordinary people, soldiers, and three types of objects to buy tickets. Obviously, they all go to buy tickets, but their final effects are different:
Students – discounted fares, ordinary people – full price tickets, military – priority tickets. This is polymorphic behavior in real life.
π2. Definition and implementation of polymorphism
π Static polymorphism
Static polymorphism is determined when the program is compiled. Among them, function overloading and templates implement polymorphism.
#include<iostream> using namespace std; int main() { int a; double da; cin >> a; cin >> da; cout << a << endl; cout << da << endl; int i = 0, j = 1; double d = 1.1, e = 2.2; swap(i, j); swap(d, e); return 0; }
β The input and output of the above call, the input of a and ch, and the output call different operator>> and operator<<
Including the exchange of the int type and the double type, the swap function is also called differently.
π Dynamic Polymorphism
First we need to introduce two concepts, virtual functions and rewriting of virtual functions
π virtual function
β Virtual function: that is, a class member function modified by virtual is called a virtual function.
class Person { public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; } };
BuyTicket() is a virtual function.
In the previous article, when inheriting, adding the virtual keyword in front of the inheritance method is virtual inheritance
π Rewriting (overriding) virtual functions
β Rewriting (overwriting) 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 and the base class virtual function are exactly the same), called sub A class's virtual function overrides the base class's virtual function.
ps: Here we don't confuse the hiding in inheritance with the rewriting in polymorphism, there is a summary below
class Person { public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; } }; class Student : public Person { public: virtual void BuyTicket() //Completed the rewrite of the parent class virtual function BuyTicket { cout << "buy tickets-half price" << endl; } };
π Condition for dynamic polymorphism
- Under inheritance, the subclass completes the rewriting of the virtual function of the parent class.
- Use the pointer or reference of the superclass to call the virtual function overridden by the subclass.
ps: If you use the parent class object to call the virtual function rewritten by the subclass, it will not constitute polymorphism, and the virtual function of the parent class will be called in the end.
Code demo:
#include<iostream> using namespace std; class Person {//base class public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; }//parent class virtual function }; class Student : public Person { public: virtual void BuyTicket() { cout << "buy tickets-half price" << endl; } }; class Soldier : public Person { public: virtual void BuyTicket() { cout << "priority-buy tickets" << endl; } }; void f(Person& p) { //Different types of objects are passed, and different functions are called, realizing various forms of calling p.BuyTicket(); } void f(Person* p) { //Different types of objects are passed, and different functions are called, realizing various forms of calling p->BuyTicket(); } int main() { Person p; // ordinary people Student st; // student Soldier so; // soldier f(p); f(st); f(so); cout << endl; f(&p); f(&st); f(&so); cout << endl; return 0; }
β The above code demonstrates that different objects (ordinary people, students, soldiers) call the same ButTicket() function, and finally achieve different effects.
β Let's demonstrate that if there is no polymorphism (using the parent class object to call the virtual function of the subclass), see what the effect is
#include<iostream> using namespace std; class Person {//base class public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; }//parent class virtual function }; class Student : public Person { public: virtual void BuyTicket() { cout << "buy tickets-half price" << endl; } }; class Soldier : public Person { public: virtual void BuyTicket() { cout << "priority-buy tickets" << endl; } }; void f(Person p)//Here we use the object of the parent class to call the virtual function of the subclass, which does not meet the conditions for polymorphism { p.BuyTicket(); } int main() { Person p; // ordinary people Student st; // student Soldier so; // soldier f(p); f(st); f(so); cout << endl; return 0; }
β The final effect is that all virtual functions of the parent class are called
π Those exceptions that also constitute dynamic polymorphism
1. Covariance (just understand)
Covariance (the return value type of the base class and the derived class virtual function is different), requires: that is, the base class virtual function returns a pointer or reference to the base class object, and when the derived class virtual function returns a pointer or reference to the derived class object, it is called covariance .
You can see that the following constructs do not constitute polymorphism.
#include<iostream> using namespace std; class a{}; class b : public a {};//Parent-child relationship--respectively do the return values ββof the following parent-child relationship virtual functions class person { public: virtual a* f() { cout << "a* person::f()" << endl; return new a; } }; class student : public person { public: virtual b* f() { cout << "b* student::f()" << endl; return new b; } }; void func(person& p) { p.f(); } void func(person* pp)//function overloading { pp->f(); } int main() { person p; student s; func(p); func(s); cout << endl; func(&p); func(&s); return 0; }
β The return type of the above virtual function of student and the virtual function of person are different, but the return type of the virtual function of person is the pointer of A (the parent class pointer), and the return type of the student virtual function is the pointer of B (the subclass pointer) . It conforms to the covariant situation, so it also constitutes polymorphism.
β ps: It is recommended not to write covariance
2. The virtual function rewriting of the subclass can not add the virtual keyword
Because it is believed that the subclass inherits from the parent class and inherits the virtual attributes of the parent class, even if the virtual keyword is not written, it is considered to be a virtual function, thus constituting polymorphism.
Students can remove the virtual keyword of the rewritten virtual function of the subclass in the above code, which also constitutes polymorphism.
3. Override of destructor ((the base class and the derived class destructor have different names)
If the polymorphism composition conditions are strictly adhered to, the subclass destructor cannot be rewritten even if it is defined as a virtual function, because the destructors of the parent and child classes cannot have the same name.
The compiler does one thing, all the destructors of the parent and child classes change the destructor name of the parent and child classes into destructor(), the purpose is to make the destructors of the parent and child classes form polymorphism.
β This also solves the problem of our last article: why are the destructors of the parent class and the subclass hidden because their destructors have the same name. If it does not constitute polymorphism, then it constitutes hiding.
So why should the destructor of the parent class and the destructor of the child class be polymorphic? Because the following scenarios will occur
#include<iostream> using namespace std; class Person { public: ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: ~Student() { cout << "~Student()" << endl; } }; int main() { Person* p1 = new Person; Person* p2 = new Student; delete p1; delete p2; return 0; }
β When we use new to apply for a subclass space, and we want to use the parent class pointer to point to this subclass space, we do not rewrite the destructor like the above code, then the delete p2 below will be wrong , the result is as follows
β And when we complete the rewrite of the destructor of the subclass (add virtual before the destructor of the parent and subclass), the result is correct
ps: We recommend that subclasses rewrite the destructor of the parent class, so that not only polymorphism can be achieved, but also normal use is not affected
π3. How to prevent the virtual function of the parent class from being overridden – final
class Car { public: virtual void Drive() final {} }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-comfortable" << endl; } };
-
When we add the final keyword after a virtual function, the virtual function cannot be overridden by the virtual function of the subclass
So the above code will report an error!
-
In the previous article inheritance, when the final keyword is added after a class, the class cannot be inherited.
Be clear about the different uses of this keyword.
π4. How to force the completion of virtual function rewriting –override
β If C language and C++ are regarded as two brothers, then C language is more like a free child, while C++ is more like a disciplined child, for example, C++ has encapsulation and cannot access member variables of a class casually
β
β And override is like a teacher who monitors whether you have completed the rewriting of virtual functions. If you add override after the function and do not complete the rewriting of virtual functions, the program will report an error.
#include<iostream> using namespace std; class Car{ public: virtual void Drive(char ch){} }; class Benz :public Car { public: virtual void Drive(int ch) override { cout << "Benz-comfortable" << endl; } }; int main() { return 0; }
β The requirements for virtual function rewriting are: the virtual function rewritten by the subclass must be completely consistent with the virtual function of the subclass (function return type, function name, function parameters)
And the virtual function of the subclass of the above code is different from the function parameter of the virtual function of the parent class, then the program will report an error, because you have not completed the rewrite of the virtual function
So when we write the destructor of the subclass, we can add an override to help us check whether the virtual function has been rewritten.
π5. Abstract class
β In real life:
β Student is a concrete class, because students can define an object Xiaoming, Xiaohong, Xiaogang, etc. (Primary school villain group)
β Dog is a concrete class, because it allows dogs to define objects Xiaobai, Wangcai, Lewinsky, etc.
β But plants are not a concrete class, because they cannot be used to define objects. It is too abstract and too broad. Strictly speaking, humans and animals cannot be called concrete classes, and the objects they define cannot be grouped into one class.
β Like this, we usually call it an abstract class, and an abstract class is only used for inheritance, not for defining objects.
Before introducing abstract classes, let's first introduce what a pure virtual function is
π Pure virtual function
class car { public: virtual void drive() = 0 { cout << "class car()"; } };
β When declaring a virtual function, add an assignment of 0, then this virtual function is a pure virtual function
A class whose virtual function is a pure virtual function is called an abstract class
π Purpose of abstract classes
#include<iostream> using namespace std; class car { public: virtual void drive() = 0; }; class benz :public car { public: void drive() { cout << "benz-comfortable" << endl; } }; class bmw :public car { public: virtual void drive() { cout << "bmw-manipulate" << endl; } }; int main() { //car c;//error abstract class car, cannot define object //normal object benz b; bmw bm; b.drive(); bm.drive(); cout << endl; //polymorphism car* pbenz = new benz; pbenz->drive(); car* pbmw = new bmw; pbmw->drive(); cout << endl; car& rbanz = b; car& rbmw = bm; rbanz.drive(); rbmw.drive(); cout << endl; return 0; }
β As we said when we talked about virtual functions, even if the subclass virtual function does not add virtual in the rewriting of the virtual function, the virtual function can also be rewritten, because inheritance makes the object function of the subclass have a virtual function!
β Similarly, in the above code, due to the inheritance of the subclass, the pure virtual function of the parent class will be inherited. If the pure virtual function is not rewritten, the subclass will also become an abstract class, which will make the subclass. Class cannot define object!
β Summary: To inherit a subclass of an abstract class, you must complete the rewrite of the pure virtual function of the abstract class, otherwise you cannot use the subclass to create objects. Like this kind of inheritance of abstract classes, we usually call it interface inheritance, which can be understood as, If you play polymorphism, you can use abstract classes, if you don't play polymorphism, don't play abstract classes
π6. The underlying principle of virtual functions
πWritten question: When there is a virtual function in a class, calculate the memory size of this class
#include<iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Func1()" << endl; } virtual void Func2() { cout << "Func2()" << endl; } void Func3() { cout << "Func3()" << endl; } private: int _b = 1; char _ch = '\0'; }; int main() { cout << sizeof(Base) << endl; Base bs; return 0; }
Can you run it yourself and see what the answer is?
πVirtual function table pointer (vtable pointer)
β We can observe that the members of the object bs are different from what we think through the vs monitoring window
β A virtual function table pointer (virtual table pointer) is placed in the first position of the member list, so the above answer will be 12 (memory alignment is also performed)
πVirtual function table (vtable)
According to the name of the above pointer - the virtual table pointer also knows that it must point to the virtual table, let's use the memory window of vs to see the effect
β
β That is to say, the virtual table pointer (the address of the first element of the virtual table) in the member variable points to the virtual table (array of function pointers), and the virtual table stores the address of the virtual function
π When are vtable pointers and vtables generated?
Not yet in the constructor:
Into the constructor (initializer list):
β Because the virtual table pointer is also counted as a member variable, as mentioned in the class and object, the member variable is defined in the initialization list, so the virtual table pointer is generated in the constructor
β And the virtual function must determine the address during the compilation process, so the virtual table is determined at compile time
π7. The underlying principle of polymorphism
β I don’t know if you have any doubts: Why does polymorphism require the reference or pointer of the parent class to call the virtual function of the subclass, and why can’t the object of the parent class be used correctly?
π Inheritance and Override of Virtual Tables
π There is only one virtual function in the parent class
Let's continue to take the example of buying tickets to explain
#include<iostream> using namespace std; class Person {//base class public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; }//parent class virtual function }; class Student : public Person { public: virtual void BuyTicket() { cout << "buy tickets-half price" << endl; } }; class Soldier : public Person { public: virtual void BuyTicket() { cout << "priority-buy tickets" << endl; } }; void f(Person& p) { //Different types of objects are passed, and different functions are called, realizing various forms of calling p.BuyTicket(); } void f(Person* p) { //Different types of objects are passed, and different functions are called, realizing various forms of calling p->BuyTicket(); } int main() { Person p; // ordinary people Student st; // student Soldier so; // soldier f(p); f(st); f(so); cout << endl; f(&p); f(&st); f(&so); cout << endl; return 0; }
β This is also one of the reasons why the respective functions are called after the rewrite is completed, and the function of the parent class is called if the rewrite is not completed.
π There are multiple virtual functions in the parent class
#include<iostream> using namespace std; class Base { public: Base() { cout << "Base()" << endl; } virtual void Func1() { cout << "virtual Base::Func1()" << endl; } virtual void Func2() { cout << "virtual Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "virtual Derive::Func1()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; return 0; }
β In the above code, there are two virtual functions in the parent class, but only one of the virtual functions is rewritten in the subclass. We use the vs monitor window to demonstrate the following effects!
π The reason why polymorphism can only be constituted by using parent class pointers or references
#include<iostream> using namespace std; class Person { public: virtual void BuyTicket() { cout << "buy tickets-Full price" << endl; } int _p = 1; }; class Student : public Person { public: virtual void BuyTicket() { cout << "buy tickets-half price" << endl; }//Overrides (rewrites) int _s = 2; }; void Func(Person& p) { p.BuyTicket(); } void Func(Person* p) { p->BuyTicket(); } int main() { Person Mike; Func(Mike); Func(Mike); Student Johnson; Johnson._p = 10; Func(Johnson); Func(Johnson); return 0; }
π call by reference
π Pointer call
πObject call
So the parent class object to call the subclass's rewritten virtual function cannot constitute polymorphism, because at this time, there is no virtual table of the subclass object in the parent class object at all.
π8. Virtual function in memory storage location
Where are virtual functions stored in memory? Please design a program to test
- stack
- heap
- Data segment (static area)
- Code segment (constant area)
β We can print the addresses of stack, heap, data segment, code segment and virtual function below for a comparison
Here is a review of the following memory distribution: (graphic)
Image Source:
(157 messages) C++ Graphical Template - Turtle Moving Forward Blog - CSDN Blog
β
β Now the main problem is how to get the address of the virtual function, because the virtual table pointer is the first in the member variable, and the size of a pointer is 4 bytes (in a 32-bit environment)
, we can get the address of the object, take out the address content of the first four bytes, we can get the address of the virtual table pointer, and then dereference to get the address of the first virtual function
#include<iostream> using namespace std; class Base { public: Base() { cout << "Base()" << endl; } virtual void Func1() { cout << "virtual Base::Func1()" << endl; } virtual void Func2() { cout << "virtual Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "virtual Derive::Func1()" << endl; } private: int _d = 2; }; int j = 0; int main() { // Take the virtual table address and print it Base b; Base* p = &b;//take its address, then take out the address of the first four bytes printf("Address of virtual function:%p\n", *(int*)p);//The virtual table pointer is dereferenced again, and the address of the virtual function is obtained. int i = 0; printf("Address on the stack:%p\n", &i); printf("The address of the data segment:%p\n", &j); int* pi = new int; printf("Address on the heap:%p\n", pi); char* ptr = "abcdefg"; printf("The address of the code segment:%p\n", ptr); return 0; }
π9. How to print the virtual table on the screen – and know which virtual function address is
We used two methods to show you the bottom layer of polymorphism.
- vs watch window – but watch window doesn’t have to be real
- memory window for vs
The third method is introduced here: printing the virtual table
β Through the above learning, we know that the virtual table is an array of function pointers (the function types may be different), since we can get the virtual table pointer - that is, the first address of the virtual table
We can also print out the contents of the function pointer array (virtual function address) like an array, and open it up.
#include<iostream> using namespace std; class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } private: int a = 1; }; 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 = 2; }; //Rename the function pointer type to him typedef void(*VFunc)();//Due to the complexity of the function pointer name, let's rename it void PrintVFT(VFunc* ptr) //Store ptr is the virtual table pointer { printf("vtable pointer:%p\n", ptr); for (int i = 0; ptr[i] != nullptr; ++i) { printf("VFT[%d]:%p->", i, ptr[i]); ptr[i]();//The underlying method of calling the function } printf("\n"); } int main() { //First get the address of the function virtual table Base b; int p = *(int*)&b;//First get the value of the virtual table address PrintVFT((VFunc*)p);//Then force the virtual table address to the following Derive d; p = *(int*)&d; PrintVFT((VFunc*)p); return 0; }
β In the above code, Derive inherits Base, rewrites func1, but does not rewrite func2
So func1 calls its own, func2 calls the parent class, and then func3 and func4 are both their own subclasses
This method is really awesome, very intuitive, Banye also calls you the strongest
π10. Override of virtual table under multiple inheritance
#include<iostream> 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(*VFunc)(); void PrintVFT(VFunc* ptr) // pointer to an array of function pointers { printf("vtable pointer:%p\n", &ptr); printf("virtual table address:%p\n", ptr); for (int i = 0; ptr[i] != nullptr; ++i) { printf("VFT[%d]:%p->", i, ptr[i]); ptr[i](); } printf("\n"); } int main() { Base1 b1; Base2 b2; Derive d; int p = *(int*)&d;//Get the value of the first virtual table address PrintVFT((VFunc*)(p)); char* pc = ((char*)&d + sizeof(Base1));//get the address of the second vtable pointer p = *(int*)pc;//Get the value of the second virtual table address PrintVFT((VFunc*)(p)); return 0; }
β In the above code: Derive inherits Base and Base and rewrites func1, but both Base1 and Base2 have fun1. How will the virtual table of the subclass be overwritten and inherited?
Base1 is inherited first, and then Base2 is inherited, so there are two virtual table pointers in the d member, the virtual table pointer of Base1 is in the front, and the virtual table pointer of Base2 is in the back
Look carefully at the above code to see how to get the second virtual table pointer
β We found that func1 of Base1 and Base2 have been rewritten, and Derive's own virtual function is only placed in the first virtual table, not in the first virtual table
π11. Diamond Inheritance – Combination of virtual inheritance and virtual function (just understand)
β In the previous article, we also left a question, which is why the initial position in the virtual base table is 0, and the problem is solved
#include<iostream> using namespace std; class A { public: virtual void func() { cout << "A::func()" << endl; } public: int _a; }; // class B : public A class B : virtual public A { public: virtual void func()//This is a virtual inheritance from A, and a virtual table will not be generated. { cout << "B:func()" << endl; } virtual void func1()//This is B's own virtual function, which will generate a virtual table { cout << "B:func1()" << endl; } public: int _b; }; // class C : public A class C : virtual public A { public: virtual void func()//This is a virtual inheritance from A, and a virtual table will not be generated. { cout << "C::func()" << endl; } virtual void func1()//This is C's own virtual function, which will generate a virtual table { cout << "C::func1()" << endl; } public: int _c; }; class D : public B, public C { public: virtual void func() { cout << "D::func()" << endl; } virtual void func1() { cout << "D::func1()" << endl; } public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
Let's call the monitoring window and the memory window to see the bottom layer
β When virtual inheritance and virtual base table exist at the same time, then there are both virtual table pointers and virtual base table pointers in member variables.
And we found:
The position of the virtual base table is the offset of the virtual table pointer to the virtual base table pointer, and the example in our previous article does not have a virtual table pointer, so the first address is 0
π12. Classic example
πThe order of diamond inheritance constructors
using namespace std; class A{ public: A(char *s) { cout << s << endl; } ~A(){} }; class B :virtual public A { public: B(char *s1, char*s2) :A(s1) { cout << s2 << endl; } }; class C :virtual public A { public: C(char *s1, char*s2) :A(s1) { cout << s2 << endl; } }; class D :public B, public C { public: // The execution order of the initial list is related to the declaration. The declaration order of inherited members is calculated according to the inheritance order. D(char *s1, char *s2, char *s3, char *s4) :B(s1, s2) , C(s1, s3) , A(s1) { cout << s4 << endl; } }; int main() { D *p = new D("class A", "class B", "class C", "class D"); delete p; return 0; }
β Everyone think about the output of the program, and the answer can be run
πRewrite of virtual function
class A { public: A() {} virtual inline void func(int val = 1){ std::cout << "A->" << val << std::endl; } virtual void test(){ func(); } }; class B : public A { public: void func(int val = 10000000){ std::cout << "B->" << val << std::endl; } }; int main(int argc, char* argv[]) { B*p = new B; p->test(); return 0; }
β In the above code, B inherits from A. When rewriting, the return value, function name, and function parameters must be consistent with those in A (the default value of A is also given to B, and the default value in B is invalid )
This question is a Bug in C++ design
π13. Summary
The difference between function overloading, hiding (redefining), and overriding (overriding)
-
function overloading
Condition: The function name is the same and the function parameters are different, and the two functions must be in the same scope
-
hide (redefine)
Condition: In inheritance, the member variable has the same name, and the member function has the same name.
-
rewrite (overwrite)
Condition: The virtual function of the subclass must be exactly the same as the virtual function of the parent class (function name, function parameters, function return value)
The requirements for overriding are stricter than those for hiding. When the function names in inheritance are the same, it is either overridden or hidden.
π14. Polymorphic interview questions
- What is polymorphism?
- What is overloading, overriding (overriding), redefining (hidden)?
- How does polymorphism work?
- Can an inline function be a virtual function? Answer: No, because the inline function has no address and cannot put the address in the virtual function table.
- Can a static member be a virtual function? Answer: No, because the static member function does not have this pointer, the virtual function table cannot be accessed using the calling method of type::member function, so the static member function cannot be placed in the virtual function table.
- Can a constructor be a virtual function? Answer: No, because the vtable pointer in the object is initialized during the constructor initialization list phase.
- Can a destructor be a virtual function? Under what circumstances is a destructor a virtual function? Answer: Yes, and it is better to define the destructor of the base class as a virtual function.
- Is it faster for an object to access a normal function or a virtual function? 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 to be called is faster. Because of polymorphism, calling a virtual function at runtime needs to look up the virtual function table.
- At what stage is the virtual function table generated and where does it exist? Answer: The virtual function table is generated in the compilation stage, and there is a code segment (constant area) in general.
- C++ diamond inheritance problem? The principle of virtual inheritance? Be careful not to confuse the virtual function table with the virtual base table here.
- What is an abstract class? The role of abstract classes? Abstract classes forcefully override virtual functions, and abstract classes reflect interface inheritance.
If the answer is not clearly written, it is all in the article