Catalog
- Definition of polymorphism
- Compile-time polymorphism
- Runtime Polymorphism
- Implementation principle of virtual function
- Instance analysis of mechanism of virtual function table
What is polymorphism
Polymorphic C++ is one of the three main features of object-oriented (OOP) language (the other two are encapsulation and inheritance)
Polymorphisms are simply summarized as "one interface, many methods".It refers to the different actions taken by the same object when it receives different messages or when different objects receive the same message.
Polymorphism has three important components:
1. Same function name
2. Context-dependent
3. Different implementation mechanisms
The advantage of polymorphism is that different tasks are distributed using as few function names as possible, and contextual information is used to determine how they are implemented.
Implementation of polymorphism: function overload, template function and virtual function
Compile-time polymorphism
A polymorphism is defined at compile time and determines how a function with the same function name is called using parameters.Includes function overloads and template functions
- Function overloading: Functions with different parameters are allowed to have the same name, primarily by renaming them from different parameter lists to achieve polymorphism.So functions that overload must have different parameter lists.One problem, however, is that it cannot overload the return values.A function template is needed to complete the polymorphism.
- Template function: A custom type template is used to define a function, that is, the parameter type of the function parameter is undetermined, but the number of parameters of the function is determined.Using function templates alone is not polymorphic, because although different types of the same function calls are implemented, the same template function definition is still used.The core needs to use the functionality of template specialization to specialize functions that need to be polymorphic to achieve polymorphism.Tip: Template functions can be overloaded just like normal functions to fully reflect polymorphism.
Runtime Polymorphism
Functions are functions that can only be determined during the execution of a program to be actually executed.
Key concepts
Virtual function: The implementation allowed in a derived class that exists in a base class is different from the base class.Represented by virtual keyword.
Virtual function table pointer: A pointer added by the compiler to the virtual function address table for each class with virtual functions.
Virtual function table: A table that records the addresses of virtual functions currently inherited and customized.
Subtypes: If type X extends or implements type Y, then X is a subtype of Y. Note: The difference between subtypes and inheritance, subtypes are used to express interface compatibility. When B is a subtype of A, it means that all operations on A can be performed on B. Inheritance tends to express the reuse of implementation, that is, the operation of B reusing A.Do to achieve their own operations.
Transition up: Convert an object of a subtype to an object of a parent type.In the process of upward transformation, there are three points to pay attention to.It is safe to shift up.Second, the upward transition can be completed automatically.Third, subtype information will be lost in the process of upward transformation.
Implementation of polymorphism
A function declared in the base class to be the virtual keyword is overloaded by the derived class, which implements polymorphism by calling the virtual function content implemented by the derived class when pointing to the derived class with the base class pointer.
Why can't runtime polymorphisms be determined at compile time?
You never know what type of object is provided in the run, only you can use the base class pointer to point to it.
//For example, for this function, the direction of the base class pointer is determined only by code, while the compilation time cannot determine the direction of the base class pointer for this function. Only at runtime, for each given code, the corresponding derived class object is dynamically returned. base* do_something(int code) { switch(code){ case 1: return new A(); case 2: return new B(); default; return nullptr; } }
Why pointers or references are needed to implement runtime polymorphism
Consider the following functions
class b{ public: virtual void print(){cout<<"here is the base"<<endl;} }; class b1: public b{ public: void print(){cout<<"here is the b1"<<endl;} }; class b2:public b{ public: void print(){cout<<"here is the b2"<<endl;} }; void print_result(b a){ a.print(); } void print_result_ref(b& a){ a.print(); } int main() { b1 a; b2 b; print_result(a); print_result(b); print_result_ref(a); print_result_ref(b); }
The output is:
here is the base here is the base here is the b1 here is the b2
When you go to a copy of a derived class object, you get the contents of the base class virtual function. While it is clear that the derived class redefines the base class virtual function, you should return the contents of the derived class. In fact, there is an upward transition of subtypes, which implicitly converts the derived class to the object of the base class and deletes the derived class.The contents of the superfluous base class so that only the functions and other contents of the base class can be called.When using a reference, C++ runs with a base class pointer to the derived class object, and gets the address of the real virtual function from the virtual function table lookup table, resulting in the output of the derived class.
Why do destructors need to be written as virtual functions?
To prevent memory leaks, when the base class pointer is used to point to a derived class object, the process of destroying the object only calls the destructor of the base class, so there will be values that destruct the content of the base class, not the exclusive content of the derived class.Destructor.
Implementation principle of virtual function
With the function table pointer pointing to the starting position of a virtual function table, the virtual function table stores the virtual function pointer in this class, the virtual function table pointer can be found when calling, the virtual function table pointer can be found through the virtual function table pointer, the function entrance can be found by using the offset of the virtual function table, and the virtual function to be used can be found.
When instantiating a subclass object of a class, if there is no virtual function defined in the subclass and the virtual function of the parent class is inherited, the subclass object will also have a virtual function table with the same function address as the parent class.If the subclass defines a virtual function inherited from the parent, the subclass overrides the function pointer of the parent class
Analysis of the Action Mechanism of Virtual Function Table
//base class class base{ public: base(long m1 = 1, long m2 = 2):m1(m1),m2(m2){}; virtual void virtualbase1() { std::cout<<"this is the base1 vitual funciton"<<endl; } virtual void virtualbase2() { std::cout<<"this is the base2 vitual funciton"<<endl; } virtual void virtualbase4() { std::cout<<"this is the base3 vitual funciton"<<endl; } private: long m1; long m2; }; //Derived Class class base1 : public base{ void virtualbase2() { std::cout<<"this is the base from subclass virtual function"<<endl; } };
The base class contains two variables m1,m2, and three virtual functions
Re-implementation of a class virtual function in a derived class
int main(){ base b; long * bAddress = (long *)&b; //Address of output base class cout<<"bAddress: "<<bAddress<<endl; //Address of output virtual table pointer long * vtptr = (long *)*(bAddress + 0); cout<<"\t vtptr:"<<vtptr<<endl; //Address of the first virtual function in the output virtual table long *pfunc1 = (long *)*(vtptr + 0); cout<<"\t vfunc1:"<<pfunc1<<endl; //The address of the second virtual function in the output virtual table long *pfunc2 = (long *)*(vtptr + 1); cout<<"\t vfunc2:"<<pfunc2<<endl; //The address of the third virtual function in the output virtual table long *pfunc3 = (long *)*(vtptr + 2); cout<<"\t vfunc3:"<<pfunc3<<endl; //Getting the content of private object m1 using the object's address offset long m1 = (long)*(bAddress + 1); cout<<"m1: "<<m1<<endl; //Similarly, get the content of private object m2 long m2 = (long)*(bAddress + 2); cout<<"m2: "<<m2<<endl; //Convert pointer to function pointer void (* pfunc1_fun)() = (void (*)()) pfunc1; //Get the result of the function pfunc1_fun(); //New Derived Class Object b1 base1 b1; //Get the address of the object long * b1Address = (long *) &b1; cout<<"b1Address: "<<b1Address<<endl; //Get the address of the virtual table pointer long * b1vtptr = (long *)*(b1Address + 0); cout<<"\t b1vtptr: "<<b1vtptr<<endl; //Gets the address of a function in a virtual table long * b1pfunc1 = (long *)*(b1vtptr + 0); cout<<"\t b1pfunc1: "<<b1pfunc1<<endl; //Gets the address of a function in a virtual table long * b2pfunc2 = (long *)*(b1vtptr + 1); cout<<"\t b1pfunc2: "<<b2pfunc2<<endl; //Gets the address of a function in a virtual table long * b3pfunc3 = (long *)*(b1vtptr + 2); cout<<"\t b1pfunc3: "<<b3pfunc3<<endl; //Convert pointer to function pointer void(* b2pfunc2_fun)() = (void(*)()) b2pfunc2; //Get the result of the function b2pfunc2_fun(); return 0; }
Run these functions to get the results:
bAddress: 0x7ffeeb90f9f0 vtptr:0x1042f7528 vfunc1:0x1042f3a50 vfunc2:0x1042f3a90 vfunc3:0x1042f3ad0 m1: 1 m2: 2 this is the base1 vitual funciton b1Address: 0x7ffeeb90f998 b1vtptr: 0x1042f7560 b1pfunc1: 0x1042f3a50 b1pfunc2: 0x1042f3b60 b1pfunc3: 0x1042f3ad0 this is the base from subclass virtual function
The result shows that when a derived class redefines a function of the base class, the pointer in its virtual function table overrides, and where it is not redefined, the address of the virtual function of the base class is maintained.