Virtual functions and polymorphism

01 virtual function

  • In the definition of a class, there isvirtualKeyword member functions are called virtual functions;
  • virtualThe 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:
    1. If the pointer points to an object of the base class, then it is called a virtual function of the base class.
    2. 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:
    1. If the reference refers to an object of the base class, then it is a virtual function of the base class.
    2. 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:

  1. Written for each hero classAttack,FightBackHurtedMember functions.
  • AttackFunction represents attack action;
  • FightBackFunction represents counterattack action;
  • HurtedThe function reduces health and represents damage actions.
  1. Set the base classCHero, 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-26paThe pointer points to thetaBClass object, sopa->Func()Call isBClass object virtual functionFunc(), the output isB::Func
  • The purpose of lines 29 through 30 is to bringAClass to store the first 8 bytes of the vtable pointer top1The pointer andBClass to store the first 8 bytes of the vtable pointer top2Pointer;
  • Line 32 of code is designed to putAClass to assign the virtual table pointer toBClass “virtual table pointer”, so equivalent to theBClass “virtual table pointer” is replacedAClass “virtual table pointer”;
  • Because of line 32, theBClass “virtual table pointer” is replacedAClass, so line 33 calls vtable pointerAClass 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