C + + understand the realization principle of polymorphism

Virtual function and polymorphism

01 virtual function

  • In the definition of a class, a member function with a virtual keyword in front of it is called a virtual function;
  • The virtual keyword is only used in the function declaration in the class definition, not when writing the function body.
class Base 
{
    virtual int Fun() ; // virtual function
};

int Base::Fun() // virtual fields are not defined at function body time
{ }

02 polymorphic form 1

  • A derived class pointer can be assigned to a base class pointer;
  • When calling the virtual function with the same name in the base class and derived class through the base class pointer:
    1. If the pointer points to an object of a base class, the called is
      Virtual function of base class;
    2. If the pointer points to an object of a derived class, it is called
      Is a virtual function of a derived class.

This mechanism is called "polymorphism", that is to say, white dot is to call which virtual function depends on which type of object the pointer object points to.

// Base class
class CFather 
{
public:
    virtual void Fun() { } // virtual function
};

// Derived class
class CSon : public CFather 
{ 
public :
    virtual void Fun() { }
};

int main() 
{
    CSon son;
    CFather *p = &son;
    p->Fun(); //Which virtual function to call depends on which type of object p points to
    return 0;
}

In the above example, the P pointer object points to the CSon class object, so p - > Fun() calls the function of the Fun member in the CSon class.

Form 2 of 03 polymorphism

  • An object of a derived class can be assigned to a reference of the base class
  • When calling "virtual function" with the same name in the base class and derived class by reference to the base class:
    1. If the reference refers to an object of a base class, the
      Use is the virtual function of the base class;
    2. If the reference refers to an object of a derived class, the
      The virtual function of the derived class is called.

This mechanism is also called "polymorphism", which means that the white dot is the virtual function to be called, depending on the type of object to be referenced.

// Base class
class CFather 
{
public:
    virtual void Fun() { } // virtual function
};

// Derived class
class CSon : public CFather 
{ 
public :
    virtual void Fun() { }
};

int main() 
{
    CSon son;
    CFather &r = son;
    r.Fun(); //Which virtual function to call depends on which type of object r references
    return 0;
}
}

In the above example, R refers to the object of the CSon class, so r.Fun() calls the function of the Fun member in the CSon class.

04 simple example of polymorphism

class A 
{
public :
    virtual void Print() { cout << "A::Print"<<endl ; }
};

// Inheritance class A
class B: public A 
{
public :
    virtual void Print() { cout << "B::Print" <<endl; }
};

// Inheritance class A
class D: public A 
{
public:
    virtual void Print() { cout << "D::Print" << endl ; }
};

// Inheriting the B class
class E: public B 
{
    virtual void Print() { cout << "E::Print" << endl ; }
};

The relationships among class A, class B, class E and class D are as follows:

int main() 
{
    A a; B b; E e; D d;
    
    A * pa = &a; 
    B * pb = &b;
    D * pd = &d; 
    E * pe = &e;
    
    pa->Print();  // a.Print() called, output: A::Print
    
    pa = pb;
    pa -> Print(); // b.Print() called, output: B::Print
    
    pa = pd;
    pa -> Print(); // d.Print() called, output: D::Print
    
    pa = pe;
    pa -> Print(); // e.Print() called, output: E::Print
    
    return 0;
}

05 polymorphism

Using polymorphism in object-oriented programming can enhance the extensibility of the program, that is, when the program needs to modify or add functions, there is less code that needs to be changed and added.

Example of LOL hero League game

Next, we use the hero example of designing LOL hero League game to explain why polymorphism can change the code less when modifying or adding functions.

LOL hero League 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. There are corresponding actions when attacking the enemy and being attacked. The actions are realized by the member function of the object.

Here are five heroes:

  • Explorer CEzreal
  • Building CGaren
  • Blind monk
  • Endless swordsman CYi
  • Ritz CRyze

Basic ideas:

  1. Write Attack, FightBack, and Hurted member functions for each hero class.
  • Attack function represents attack action;
  • The FightBack function represents the counterattack action;
  • Hurted function means to reduce your health and show the injured action.
  1. Set the base class chro, which each hero class inherits

02 implementation of non polymorphism

// Base class
class CHero 
{
protected:  
    int m_nPower ; //Represents attack power
    int m_nLifeValue ; //Represents health
};


// Limitless Swordsmen
class CYi : public CHero 
{
public:
    // Attack function of attacking Galen
    void Attack(CGaren * pGaren) 
    {
        .... // Code that represents the attack action
        pGaren->Hurted(m_nPower);
        pGaren->FightBack(this);
    }

    // Attack function of attacking rez
    void Attack(CRyze * pRyze) 
    {
        .... // Code that represents the attack action
        pRyze->Hurted(m_nPower);
        pRyze->FightBack( this);
    }
    
    // Reduce your health
    void Hurted(int nPower) 
    {
        ... // Code showing the injured action
        m_nLifeValue -= nPower;
    }
    
    // Counterattack function of Galen
    void FightBack(CGaren * pGaren) 
    {
        ....// Code for counterattack
        pGaren->Hurted(m_nPower/2);
    }
    
    // The counterattack function of counterattack rez
    void FightBack(CRyze * pRyze) 
    {
        ....// Code for counterattack
        pRyze->Hurted(m_nPower/2);
    }
};

There are n kinds of heroes. There will be n Attack member functions and N FightBack in CYi class
Member functions. This is also true for other classes.

If the game version is upgraded and a new hero, Hanshi CAshe, is added, the program changes greatly. All classes need to add two member functions:

void Attack(CAshe * pAshe);
void FightBack(CAshe * pAshe);

This workload is very large!! Very inhumane, so this design is very bad!

03 implementation of polymorphism

The advantage of polymorphism can be known by realizing polymorphism. Then the chestnut above is changed to polymorphism as follows:

// 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 class CYi:
class CYi : public CHero {
public:
    // Attack function
    void Attack(CHero * pHero) 
    {
        .... // Code that represents the attack action
        pHero->Hurted(m_nPower); // polymorphic
        pHero->FightBack(this);  // polymorphic
    }
    
    // Reduce your health
    void Hurted(int nPower) 
    {
        ... // Code showing the injured action
        m_nLifeValue -= nPower;
    }
    
    // Counterattack function
    void FightBack(CHero * pHero) 
    {
        ....// Code for counterattack
        pHero->Hurted(m_nPower/2); // polymorphic
    }
};

If you add a new hero, Hanshi CAshe, you only need to write a new class of CAshe, and you don't need to add a new hero in the existing class

void Attack( CAshe * pAshe) ;
void FightBack(CAshe * pAshe) ;

Therefore, the existing classes can remain unchanged. When using polymorphic features to add new heroes, it can be seen that the number of changes is very small.

Polymorphic usage:

void CYi::Attack(CHero * pHero) 
{
    pHero->Hurted(m_nPower); // polymorphic
    pHero->FightBack(this);  // polymorphic
}

CYi yi; 
CGaren garen; 
CLeesin leesin; 
CEzreal ezreal;

yi.Attack( &garen );  //(1)
yi.Attack( &leesin ); //(2)
yi.Attack( &ezreal ); //(3)

According to the rule of polymorphism, the above (1), (2), (3) enter the CYi::Attack function
, respectively Calling:

CGaren::Hurted
CLeesin::Hurted
CEzreal::Hurted

Another example of polymorphism

Give a question to test you, see if you understand the polymorphic characteristics, the following code, pbase - > fun1() output results?

class Base 
{
public:
    void fun1() 
    { 
        fun2(); 
    }
    
    virtual void fun2()  // virtual function
    { 
        cout << "Base::fun2()" << endl; 
    }
};

class Derived : public Base 
{
public:
    virtual void fun2()  // virtual function
    { 
        cout << "Derived:fun2()" << endl; 
    }
};

int main() 
{
    Derived d;
    Base * pBase = & d;
    pBase->fun1();
    return 0;
}

Do you think that the pBase pointer object points to the derived class object, but there is no fun1 member function in the derived class, then the fun1 member function of the base class will be called, and the fun2 member function of the base class will be called in the Base::fun1(), so the output result is Base::fun2()?

Suppose I convert the above code, do you still think the output is Base::fun2()?

class Base 
{
public:
    void fun1() 
    { 
        this->fun2();  // this is a base class pointer, and fun2 is a virtual function, so it is polymorphic
    }
}

The function of this pointer is to point to the object that the member function acts on, so this can be directly used in non static member functions to represent the pointer to the object that the function acts on.

The pBase pointer object points to the derived class object, and there is no fun1 member function in the derived class, so the fun1 member function of the base class will be called. When this - > fun2() is executed in the body of the Base::fun1() member function, it actually points to the fun2 member function of the derived class object.

So the correct output is:

Derived:fun2()

So we need to pay attention to:

In the non constructor function, the virtual function is called polymorphic!

Is there polymorphism in constructors and destructors?

Calling virtual functions in constructors and destructors is not polymorphism. It can be determined at compile time that the function to be called is a function defined in its own class or base class, and it will not wait until run time to decide whether to call its own or derived class functions.

Let's see the following code example to illustrate:

// Base class
class CFather 
{
public:
    virtual void hello() // virtual function
    {
        cout<<"hello from father"<<endl; 
    }
    
    virtual void bye() // virtual function
    {
        cout<<"bye from father"<<endl; 
    }
};

// Derived class
class CSon : public CFather
{ 
public:
    CSon() // Constructor
    { 
        hello(); 
    }
    
    ~CSon()  // Destructor
    { 
        bye();
    }

    virtual void hello() // virtual function
    { 
        cout<<"hello from son"<<endl;
    }
};

int main()
{
    CSon son;
    CFather *pfather;
    pfather = & son;
    pfather->hello(); //polymorphic
    return 0;
}

Output results:

hello from son  // Constructor executed when constructing son object
hello from son  // polymorphic
bye from father // When the son object is deconstructed, because the CSon class does not have a bye member function, the bye member function of the base class is called

The realization principle of polymorphism

The key to polymorphism is that when a virtual function is called by a base class pointer or reference, the compiler cannot determine whether the function called is a base class or a derived class, and the runtime can determine it.

We use sizeof to calculate the sizes of classes with and without virtual functions. What is the result?

class A 
{
public:
    int i;
    virtual void Print() { } // virtual function
};

class B
{
public:
    int n;
    void Print() { } 
};

int main() 
{
    cout << sizeof(A) << ","<< sizeof(B);
    return 0;
}

On a 64 bit machine, the result of execution:

16,4

From the above results, we can see that there are 8 bytes more for classes with virtual functions, and the pointer type size on 64 bit machines is exactly 8 bytes. What's the effect of the 8 bytes more pointer?

01 virtual function table

Every class with a virtual function (or a derived class of a class with a virtual function) has a virtual function table. In any object of this class, there is a pointer to the virtual function table. The virtual function address of this class is listed in the virtual function table.

The extra 8 bytes are the address used to put the virtual function table.

// Base class
class Base 
{
public:
    int i;
    virtual void Print() { } // virtual function
};

// Derived class
class Derived : public Base
{
public:
    int n;
    virtual void Print() { } // virtual function
};

The Derived class inherits the Base class, and both classes have virtual functions. The form of its virtual function table can be understood as follows:

Polymorphic function call statements are compiled into a series of instructions to find the virtual function address in the virtual function table and call the virtual function according to the address of the virtual function table stored in the object pointed by the base class pointer (or referenced by the base class).

02 prove the function of virtual function table pointer

In the above, we use sizeof operator to calculate the size of classes with virtual functions. We find that the size of 8 bytes (64 bit system) is extra. The extra 8 bytes are pointers to the virtual function table. The virtual function address of this class is listed in the virtual function table.

The following code example is used to prove the function of virtual function table pointer:

// Base class
class A 
{
public: 
    virtual void Func()  // virtual function
    { 
        cout << "A::Func" << endl; 
    }
};

// Derived class
class B : public A 
{
public: 
    virtual void Func()  // virtual function
    { 
        cout << "B::Func" << endl;
    }
};

int main() 
{
    A a;
    
    A * pa = new B();
    pa->Func(); // polymorphic
    
    // 64 bit program pointer is 8 bytes
    int * p1 = (int *) & a;
    int * p2 = (int *) pa;
    
    * p2 = * p1;
    pa->Func();
    
    return 0;
}

Output results:

B::Func
A::Func
  • The PA pointer in lines 25-26 points to class B objects, so pa - > Func() calls the virtual function Func() of class B objects, and the output content is B::Func;
  • The purpose of lines 29-30 is to store the "virtual function table pointer" of the first 8 bytes of class A to pointer p1 and the "virtual function table pointer" of the first 8 bytes of class B to pointer p2;
  • The purpose of line 32 is to assign the "virtual function table pointer" of class A to the "virtual function table pointer" of class B, so it is equivalent to replacing the "virtual function table pointer" of class B with the "virtual function table pointer" of class A;
  • Because of the function of line 32, the virtual function table pointer of class B is replaced by the virtual function table pointer of class A, so line 33 calls the virtual function Func() of class A, and the output content is A::Func

Through the above code and explanation, it can effectively prove the function of "pointer of virtual function table", which points to "virtual function table", and "virtual function table" which stores the address of "virtual function" in the class. Then, in the calling process, polymorphism can be realized.

virtual destructor

Destructor is a function that is called automatically when an object is deleted or a program is exited. Its purpose is to release some resources.

In the case of polymorphism, when the derived class object is deleted through the base class pointer, usually only the destructor of the base class is called, which will lead to the situation that the destructor of the derived class object is not called and the resource is leaked.

Here is an example:

// Base class
class A 
{
public: 
    A()  // Constructor
    {
        cout << "construct A" << endl;
    }
    
    ~A() // Destructor
    {
        cout << "Destructor A" << endl;
    }
};

// Derived class
class B : public A 
{
public: 
    B()  // Constructor
    {
        cout << "construct B" << endl;
    }
    
    ~B()// Destructor
    {
        cout << "Destructor B" << endl;
    }
};

int main() 
{
    A *pa = new B();
    delete pa;
    
    return 0;
}

Output results:

construct A
construct B
Destructor A

As can be seen from the above output, when the pa pointer object is deleted, the destructor of class B is not called.

Solution: declare the destructor of the base class as virtual

  • The destructors of derived classes can be declared without virtual;
  • When a derived class object is deleted by pointer to the base class, the destructor of the derived class is first invoked, then the destructor of the base class is invoked, or the rule of "first construction, then fictitious" is followed.

Define the destructor of the base class in the above code as "virtual destructor":

// Base class
class A 
{
public: 
    A()  
    {
        cout << "construct A" << endl;
    }
    
    virtual ~A() // virtual destructor 
    {
        cout << "Destructor A" << endl;
    }
};

Output results:

construct A
construct B
Destructor B
Destructor A

So we should form good habits:

  • If a class defines a virtual function, the destructor should also be defined as a virtual function;
  • Alternatively, if a class is intended to be used as a base class, the destructor should also be defined as a virtual function.
  • Note: constructors are not allowed to be defined as virtual constructors.

Pure virtual functions and abstract classes

Pure virtual function: virtual function without function body

class A 
{

public:
    virtual void Print( ) = 0 ; //Purely imaginary function
private: 
    int a;
};

Classes containing 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;         // Error, A is an abstract class, cannot create an object
A * pa ;     // ok, you can define pointers and references of abstract classes
pa = new A ; // Error, A is an abstract class, object cannot be created

Recommended reading:

Understanding and function of C++ this pointer

A common feature of C + + to understand inheritance

Overload of C + + assignment operator '=' (shallow copy, deep copy)

C + + hands teach you how to implement variable length arrays

Tags: C++ less Programming

Posted on Sat, 01 Feb 2020 01:58:04 -0800 by haaglin