Virtual functions and polymorphism
01 virtual function
- In the definition of a class, there is
virtual
Keyword member functions are called virtual functions; virtual
The keyword is used only in the function declaration in the class definition, not in the function body.
class Base
{
virtual int Fun(a) ; / / virtual functions
};
int Base::Fun() // The virtual field is not defined in the function body{}Copy the code
The expression of polymorphism is one
- Pointers to derived classes can be assigned to Pointers to base classes;
- When a base class pointer is used to call a “virtual function” of the same name in a base and derived class:
- If the pointer points to an object of the base class, then it is called a virtual function of the base class.
- If the pointer points to an object of a derived class, the derived class’s virtual function is called.
This mechanism is called “polymorphism”, and the white point is which virtual function is called, depending on what type of object the pointer object points to.
/ / the base class
class CFather
{
public:
virtual void Fun(a) {}/ / virtual functions
};
/ / a derived class
class CSon : public CFather
{
public :
virtual void Fun(a) {}};int main(a)
{
CSon son;
CFather *p = &son;
p->Fun(); Which virtual function is called depends on what type of object p refers to
return 0;
}
Copy the code
The p pointer in the above example points to CSon, so p->Fun() calls the Fun member of the CSon class.
03 The expression of polymorphism is form two
- Objects of derived classes can be assigned “references” to base classes
- When a base class reference calls a “virtual function” of the same name in a base and derived class:
- If the reference refers to an object of the base class, then it is a virtual function of the base class.
- If the reference refers to an object of a derived class, then the virtual function of the derived class is called.
This mechanism, also known as “polymorphism,” essentially means which virtual function is called, depending on what type of object it refers to.
/ / the base class
class CFather
{
public:
virtual void Fun(a) {}/ / virtual functions
};
/ / a derived class
class CSon : public CFather
{
public :
virtual void Fun(a) {}};int main(a)
{
CSon son;
CFather &r = son;
r.Fun(); Which virtual function is called depends on what type of object r refers to
return 0; }}Copy the code
In the example above, r refers to a CSon object, so r.fin () calls the Fun member of CSon.
04 Simple examples of polymorphism
class A
{
public :
virtual void Print(a) { cout << "A::Print"<<endl; }};// Inherit class A
class B: public A
{
public :
virtual void Print(a) { cout << "B::Print" <<endl; }};// Inherit class A
class D: public A
{
public:
virtual void Print(a) { cout << "D::Print" << endl; }};// Inherit class B
class E: public B
{
virtual void Print(a) { cout << "E::Print" << endl; }};Copy the code
The relationship between class A, B, E and D is shown as follows:
int main(a)
{
A a; B b; E e; D d;
A * pa = &a;
B * pb = &b;
D * pd = &d;
E * pe = &e;
pa->Print(); // a.print () is called with: A::Print
pa = pb;
pa -> Print(); // b.print () is called with: B::Print
pa = pd;
pa -> Print(); // d.print () is called with: D::Print
pa = pe;
pa -> Print(); // e.print () is called with: E::Print
return 0;
}
Copy the code
Polymorphism
The use of “polymorphism” in object-oriented programming can enhance the extensibility of the program, that is, when the program needs to change or add functionality, there is less code to change or add.
LOL league of Legends game example
Let’s use the example of designing heroes in LOL league of Legends to illustrate how polymorphism can be modified or added with less code change.
LOL league of Legends is a 5V5 competitive game. There are many heroes in the game. Each hero has a “class” corresponding to it, and each hero is an “object”.
Heroes can attack each other, and there are corresponding actions when attacking enemies and being attacked, which are realized through the member functions of the object.
Here are five heroes:
- The explorer CEzreal
- Building CGaren
- Blind monk CLeesin
- CYi the Infinite Sword Master
- Ritz CRyze
Basic idea:
- Written for each hero class
Attack
,FightBack
和Hurted
Member functions.
Attack
Function represents attack action;FightBack
Function represents counterattack action;Hurted
The function reduces health and represents damage actions.
- Set the base class
CHero
, each hero class inherits from this base class
02 Non-polymorphic implementation methods
/ / the base class
class CHero
{
protected:
int m_nPower ; // represents attack power
int m_nLifeValue ; // Represents health
};
// Infinite sword master class
class CYi : public CHero
{
public:
// Attack galen's attack function
void Attack(CGaren * pGaren)
{...// The code that represents the attack action
pGaren->Hurted(m_nPower);
pGaren->FightBack(this);
}
// Attack ruiz's attack function
void Attack(CRyze * pRyze)
{...// The code that represents the attack action
pRyze->Hurted(m_nPower);
pRyze->FightBack( this);
}
// Reduces health
void Hurted(int nPower)
{...// The code that represents the injured action
m_nLifeValue -= nPower;
}
// Counter Galen's counter function
void FightBack(CGaren * pGaren)
{... .// The counter action code
pGaren->Hurted(m_nPower/2);
}
// Counter rize's counter function
void FightBack(CRyze * pRyze)
{... .// The counter action code
pRyze->Hurted(m_nPower/2); }};Copy the code
With n heroes, there are n Attack member functions and n FightBack member functions in the CYi class. The same is true for other classes.
If the version of the game is upgraded to include a new hero, Icy CAshe, the program will change a lot. All classes need to add two member functions:
void Attack(CAshe * pAshe);
void FightBack(CAshe * pAshe);
Copy the code
This is a lot of work!! Very inhumane, so this kind of design is very bad!
03 The realization of polymorphism
Use the polymorphic way to achieve, you can know the advantages of polymorphism, then the above chestnut to change the polymorphic way is as follows:
/ / the base class
class CHero
{
public:
virtual void Attack(CHero *pHero){}
virtual voidFightBack(CHero *pHero){}
virtual void Hurted(int nPower){}
protected:
int m_nPower ; // represents attack power
int m_nLifeValue ; // Represents health
};
// Derived CYi:
class CYi : public CHero {
public:
// Attack function
void Attack(CHero * pHero)
{...// The code that represents the attack action
pHero->Hurted(m_nPower); / / polymorphism
pHero->FightBack(this); / / polymorphism
}
// Reduces health
void Hurted(int nPower)
{...// The code that represents the injured action
m_nLifeValue -= nPower;
}
// counter function
void FightBack(CHero * pHero)
{... .// The counter action code
pHero->Hurted(m_nPower/2); / / polymorphism}};Copy the code
If you add a new hero, icy Ash CAshe, you only need to write a new class CAshe, no longer need to add a new hero to the existing class:
void Attack( CAshe * pAshe) ;
void FightBack(CAshe * pAshe) ;
Copy the code
So the existing class can be left intact, so using the polymorphic feature of the new hero, visible change is very little.
Polymorphic use mode:
void CYi::Attack(CHero * pHero)
{
pHero->Hurted(m_nPower); / / polymorphism
pHero->FightBack(this); / / polymorphism
}
CYi yi;
CGaren garen;
CLeesin leesin;
CEzreal ezreal;
yi.Attack( &garen ); / / (1)
yi.Attack( &leesin ); / / (2)
yi.Attack( &ezreal ); / / (3)
Copy the code
(1), (2), (3) enter the CYi::Attack function, respectively, according to the rules of polymorphism:
CGaren::Hurted
CLeesin::Hurted
CEzreal::Hurted
Copy the code
Another example of polymorphism
PBase ->fun1() : pBase->fun1()
class Base
{
public:
void fun1(a)
{
fun2();
}
virtual void fun2(a) / / virtual functions
{
cout << "Base::fun2()" << endl; }};class Derived : public Base
{
public:
virtual void fun2(a) / / virtual functions
{
cout << "Derived:fun2()" << endl; }};int main(a)
{
Derived d;
Base * pBase = & d;
pBase->fun1();
return 0;
}
Copy the code
PBase ::fun2(); Base::fun2(); Base::fun2(); Base::fun2();
If I were to convert the above code, would you still think it would output Base::fun2()?
class Base
{
public:
void fun1(a)
{
this->fun2(); // This is the base pointer, fun2 is the virtual function, so it is polymorphic}}Copy the code
The purpose of this pointer is to point to the object on which a member function functions, so we can use this directly in non-static member functions to represent a pointer to the object on which the function functions.
The pBase pointer object refers to the derived object. There is no fun1 member function in the derived class, so the fun1 member function of the Base class is called. When this->fun2() is called in the Base::fun1() member function body, it actually refers to the fun2 member function of the derived object.
So the correct output is:
Derived:fun2()
Copy the code
So we need to pay attention to:
Calling a “virtual function” in a non-constructor, non-destructor member function is polymorphic!!
Are there polymorphisms in constructors and destructors?
Calls to “virtual functions” in constructors and destructors are not polymorphic. You can determine at compile time whether the function you are calling is defined in your own class or base class. You do not wait until run time to decide whether to call your own or a derived function.
Let’s look at the following code example to illustrate:
/ / the base class
class CFather
{
public:
virtual void hello(a) / / virtual functions
{
cout<<"hello from father"<<endl;
}
virtual void bye(a) / / virtual functions
{
cout<<"bye from father"<<endl; }};/ / a derived class
class CSon : public CFather
{
public:
CSon() // constructor
{
hello();
}
~CSon() // destructor
{
bye();
}
virtual void hello(a) / / virtual functions
{
cout<<"hello from son"<<endl; }};int main(a)
{
CSon son;
CFather *pfather;
pfather = & son;
pfather->hello(); / / polymorphism
return 0;
}
Copy the code
Output result:
hello from son // The constructor executed when constructing the son object
hello from son / / polymorphism
bye from father The CSon class does not have a BYE member function, so the bye member function of the base class is called when the son object is destructed
Copy the code
The realization principle of polymorphism
The key to “polymorphism” is that when a virtual function is called through a base class pointer or reference, it is not determined at compile time, but only at run time, whether the function is called from the base class or a derived class.
If we use sizeof to calculate the sizeof a class that has virtual functions and a class that doesn’t, what happens?
class A
{
public:
int i;
virtual void Print(a) {}/ / virtual functions
};
class B
{
public:
int n;
void Print(a) {}};int main(a)
{
cout << sizeof(A) << ","<< sizeof(B);
return 0;
}
Copy the code
On a 64-bit machine, the result of execution:
16, 4Copy the code
From the result above, we can see that the class with the virtual function has 8 extra bytes. On the 64-bit machine, the size of the pointer type is exactly 8 bytes. What’s the use of the extra 8 bytes?
01 Virtual function table
Every class that has virtual functions (or a derived class from a class that has virtual functions) has a virtual table, and Pointers to the virtual table are placed in any object of that class. The virtual function table lists the virtual function addresses of this class.
The extra eight bytes are the addresses of the “virtual function table”.
/ / the base class
class Base
{
public:
int i;
virtual void Print(a) {}/ / virtual functions
};
/ / a derived class
class Derived : public Base
{
public:
int n;
virtual void Print(a) {}/ / virtual functions
};
Copy the code
The Derived class inherits from the Base class, and both classes have virtual functions. The form of the virtual function table is as follows:
Polymorphic function call statements are compiled into a series of instructions to look up the address of the virtual function in the virtual function table based on the address of the object pointed to (or referenced by) the base class reference.
02 Prove the function of virtual table Pointers
We used sizeof to calculate the sizeof the class that has virtual functions, and found that the size is 8 bytes too large (for 64-bit systems). These 8 bytes are Pointers to the virtual table. The virtual function table lists the virtual function addresses of this class.
The following code is used to demonstrate the use of “virtual table pointer” :
/ / the base class
class A
{
public:
virtual void Func(a) / / virtual functions
{
cout << "A::Func" << endl; }};/ / a derived class
class B : public A
{
public:
virtual void Func(a) / / virtual functions
{
cout << "B::Func" << endl; }};int main(a)
{
A a;
A * pa = new B();
pa->Func(); / / polymorphism
// The 64-bit program pointer is 8 bytes
int * p1 = (int *) & a;
int * p2 = (int *) pa;
* p2 = * p1;
pa->Func();
return 0;
}
Copy the code
Output result:
B::Func
A::Func
Copy the code
- Lines 25-26
pa
The pointer points to thetaB
Class object, sopa->Func()
Call isB
Class object virtual functionFunc()
, the output isB::Func
; - The purpose of lines 29 through 30 is to bring
A
Class to store the first 8 bytes of the vtable pointer top1
The pointer andB
Class to store the first 8 bytes of the vtable pointer top2
Pointer; - Line 32 of code is designed to put
A
Class to assign the virtual table pointer toB
Class “virtual table pointer”, so equivalent to theB
Class “virtual table pointer” is replacedA
Class “virtual table pointer”; - Because of line 32, the
B
Class “virtual table pointer” is replacedA
Class, so line 33 calls vtable pointerA
Class virtual functionFunc()
, the output isA::Func
Through the above code and explanation, can effectively prove the function of the “pointer to the virtual function table”, “pointer to the virtual function table” points to the “virtual function table”, “virtual function table” is stored in the class “virtual function” address, so in the call process, can achieve the characteristics of polymorphism.
Virtual destructor
Destructors are functions that are automatically called when an object is deleted or a program exits to do some resource freeing.
Then in the polymorphic scenario, when the derived object is deleted through the pointer of the base class, only the destructor of the base class is usually called, which leads to the situation that the destructor of the derived object is not called and there is resource leakage.
Look at the following example:
/ / the base class
class A
{
public:
A() // constructor
{
cout << "construct A" << endl;
}
~A() // destructor
{
cout << "Destructor A" << endl; }};/ / a derived class
class B : public A
{
public:
B() // constructor
{
cout << "construct B" << endl;
}
~B()// destructor
{
cout << "Destructor B" << endl; }};int main(a)
{
A *pa = new B();
delete pa;
return 0;
}
Copy the code
Output result:
construct A
construct B
Destructor A
Copy the code
As you can see from the output above, the destructor of class B is not called when the PA pointer object is deleted.
Solution: Declare the destructor of the base class virtual
- Destructors for derived classes may be virtual undeclared;
- When a derived object is deleted through a pointer to the base class, the destructor of the derived class is called first, then the destructor of the base class, again following the “construct first, fabricate later” rule.
Define the base class destructor in the above code as a virtual destructor:
/ / the base class
class A
{
public:
A()
{
cout << "construct A" << endl;
}
virtual ~A() // Virtual destructor
{
cout << "Destructor A" << endl; }};Copy the code
Output result:
construct A
construct B
Destructor B
Destructor A
Copy the code
So develop good habits:
- A class that defines a virtual function should define its destructor as a virtual function.
- Alternatively, if a class is intended to be used as a base class, the destructor should also be defined as virtual.
- Note: Constructors are not allowed to be defined as make-believe functions.
Pure virtual functions and abstract classes
Pure virtual function: a virtual function without a function body
class A
{
public:
virtual void Print( ) = 0 ; // A pure virtual function
private:
int a;
};
Copy the code
Classes that contain pure virtual functions are called abstract classes
- Abstract classes can only be used as base classes to derive new classes. Objects of abstract classes cannot be created
- Pointers and references to abstract classes can point to objects of classes derived from abstract classes
A a; // A is an abstract class and cannot create objects
A * pa ; // ok to define Pointers and references to abstract classes
pa = new A ; // error: A is an abstract class and cannot create objects
Copy the code
Recommended reading:
C++ this pointer understanding and functions
C++ a common feature to understand inheritance
Overloading of C++ assignment operator ‘=’ (shallow copy, deep copy)
C++ teaches you how to implement variable-length arrays