[C++ Programming] Chapter 6: Polymorphism and Virtual Functions

Table of contents

1. The basic concept of polymorphism

(1) Polymorphism

(2) virtual function

(3) Realize polymorphism through base class pointers

(4) Realize polymorphism through base class reference

(5) * The principle of polymorphism

Two, polymorphic instance

3. The use of polymorphism

4. Virtual destructor

5. Pure virtual functions and abstract classes

(1) Pure virtual function 

(2) Abstract class 

(3) Virtual base class




1. The basic concept of polymorphism

  • Polymorphism is divided into compile-time polymorphism and run-time polymorphism .
  • Compile-time polymorphism mainly refers to the overloading of functions (including operator overloading): the call to an overloaded function can determine which function should be called according to the actual parameters at compile time, so it is called compile-time polymorphism . Polymorphism during compilation is called static polymorphism .
  • Runtime polymorphism is related to concepts such as inheritance and virtual functions : the polymorphism mentioned in this chapter mainly refers to runtime polymorphism. Polymorphism at runtime is called dynamic polymorphism .

(1) Polymorphism

The program compilation phase is earlier than the program running phase, so static binding is called early binding, and dynamic binding is called late binding.

The difference between static polymorphism and dynamic polymorphism lies in when the function implementation is associated with the function call, whether it is in the compilation phase or in the runtime phase , that is, whether the function address is bound early or late.

On the premise that the assignment is compatible between classes , the following two conditions must be met to implement dynamic binding:

  1. A virtual function must be declared.
  2. Call the virtual function through the reference or pointer of the base class type .

(2) virtual function

⚫ The so-called "virtual function" is a member function with the virtual  keyword added in front of the function declaration  .

  • The virtual keyword is only used at the member function declaration in the class definition, and cannot be used when writing the member function body outside the class.
  • Static member functions cannot be virtual.
  • A class that contains virtual functions is called a "polymorphic class".
  • The general format for declaring virtual function members is as follows:
virtual 函数返回值类型 函数名(形参表);
A member function defined with the virtual keyword in the class definition becomes a virtual function.
  • Again, the declaration of a virtual function can only appear when the function prototype is declared in the class definition, not when the member function outside the class is implemented .
⚫Derived classes can inherit the same-named function of the base class, and can override this function in the derived class.
  • If the virtual function is not used, when the function is called by a derived class object and the function is rewritten in the derived class, the function of the same name in the derived class is called, that is, the function in the base class is "hidden".
⚫ Of course, if you still want to call the function of the base class, you only need to add the base class name and scope qualifier in front of the function when calling the function .
There are a few things to note about virtual functions:
  1. Although declaring a virtual function as an inline function will not cause an error, because the inline function is statically processed at the compilation stage, and the call to the virtual function is dynamically bound, so the virtual function is generally not declared as an inline function .
  2. The derived class rewrites the virtual function of the base class to achieve polymorphism, requiring the function name, parameter list and return value type to be exactly the same.
  3. A virtual function is defined in the base class, and the function always maintains the characteristics of a virtual function in the derived class.
  4. Only non-static member functions of a class can be defined as virtual functions, and static member functions and friend functions cannot be defined as virtual functions .
  5. If the definition of the virtual function is outside the class, you only need to add the virtual keyword when declaring the function, and do not add the virtual keyword .
  6. Constructors cannot be defined as virtual functions . It is best not to define operator= as a virtual function, because it is easy to be confused when using it.
  7. Do not call virtual functions in constructors and destructors . In constructors and destructors, objects are incomplete and undefined behavior may occur.
  8. It is better to declare the destructor of the base class as a virtual function .

(3) Realize polymorphism through base class pointers

  • After the virtual function is declared, the address of the derived class object can be assigned to the base class pointer, that is, the base class pointer can point to the derived class object.
  • For a statement that calls a virtual function with the same name and the same parameter table in both the base class and the derived class through the pointer of the base class, the system does not determine whether the virtual function of the base class or the derived class is to be executed at compile time;
  • And when the program runs to this statement, if the base class pointer points to a base class object, the virtual function of the base class is called;
  • If the base class pointer points to a derived class object, the virtual function of the derived class is called.

[Example] Polymorphism

[Sample code] Demonstrates the characteristics of polymorphism, using pointers of base class and derived class, and calling the appropriate virtual function at runtime:

#include <iostream>
using namespace std;

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

class B : public A { // 公有继承
public:
    virtual void Print() // 虚函数
    {
        cout << "B::Print" << endl;
    }
};

class D : public A { // 公有继承
public:
    virtual void Print() // 虚函数
    {
        cout << "D::Print" << endl;
    }
};

class E : public B { // 公有继承
public:
    virtual void Print() // 虚函数
    {
        cout << "E::Print" << endl;
    }
};

int main()
{
    A a;
    B b;
    D d;
    E e;
    A* pa = &a;   // 基类pa指向基类对象a
    B* pb = &b;   // 派生类指针pa指向派生类对象b
    pa->Print();  // 多态,目前指向基类对象,调用a.Print(),输出A::Print
    pa = pb;      // 派生类指针赋给基类指针,pa指向派生类对象b
    pa->Print();  // 多态,目前指向派生对象,调用b.Print () ,输出B::Print
    pa = &d;      // 基类指针pa指向派生类对象d
    pa->Print();  // 多态,目前指向派生类对象,调用d.Print () ,输出D::Print
    pa = &e;      // 基类指针pa指向派生对象e
    pa->Print();  // 多态,目前指向派生类对象,调用e.Print () ,输出E::Print
    return 0;
}

【Code Explanation】

  • Polymorphism is an important feature in C++ object-oriented programming. It means that the compiler does not know which function to call at compile time, and determines which function to use at run time according to the actual type of the object.
  • In this code, there is the base class  A and its two derived classes  B and   one derived class of  Dthe derived class  . Every class has a virtual function  .BEPrint()
  • In  the function, four objects , , , main()are created  .abde
  • pa Base class pointers and derived class pointers  are also declared  pb. Point pa to ​​​​​​​​, and then call the virtual function pa->Print() ​​​​​​​​. Because Print() it is a virtual function, the corresponding virtual function will be called according to the type of object pointed to by the pointer when calling. This achieves polymorphism. In the output, different outputs printed for different objects are shown separately.
  • It is worth noting that when implementing a virtual function in a derived class, it is necessary to ensure that the function signature (parameter list, return type, function name) is exactly the same as the virtual function defined in the base class. In addition, in the derived class, you can use override the keyword to mark the overloading of the virtual function, which allows the compiler to check whether the virtual function is really overloaded.

【Results of the】

  • This is because polymorphism is used in the program and virtual functions are called  Print().
  • In C++, the virtual function is called through the base class pointer pointing to the derived class object, realizing polymorphism.
  • pa->Print() When we call ​​​​​​​​, according to the type of the actual object currently pointed to, call the corresponding virtual function.
  • In this program, the pointer pa ​​​​​​​​​first points to A the object of the class a ​​​​​​​​​​​​​, so Print() when calling​​​​​​​​​​​, output "A::Print" .
  • Next, point the pointer pa to B the object of the class b​​​​​​​​, and call it Print() to output "B::Print".
  • Then point the pointer pa to D the object of the class d​​​​​​​​​​, and Print() output "D::Print" when calling​​​​​​​​​.
  • Finally, point the pointer pa ​​​​​​​​​​to Ethe object of the class e​​​​​​​​​, and Print() output "E::Print" when calling​​​​​​​​​.
  • These are all manifestations of polymorphism, that is, the same function call exhibits different behaviors according to different object types.
A::Print
B::Print
D::Print
E::Print

(4) Realize polymorphism through base class reference

  • Polymorphism can be achieved when calling a virtual function through a base class pointer, and the statement calling a virtual function through a base class reference is also polymorphic.
  • That is, when calling a virtual function with the same name and parameter table in the base class and derived class through the reference of the base class, if it refers to an object of the base class, the virtual function of the base class is called;
  • If it refers to an object of a derived class, the virtual function of the derived class is called.

[Example] Base class references and polymorphic features

[Example code] The C++ program uses base class references and polymorphic features:

#include<iostream>
using namespace std;

class A // 基类
{
public:
    virtual void Print() // 虚函数
    {
        cout << "A::Print" << endl;
    }
};

class B : public A // 公有派生
{
public:
    virtual void Print() // 虚函数
    {
        cout << "B::Print" << endl;
    }
};

void PrintInfo(A& r) // 参数为基类引用
{
    r.Print(); // 多态,使用基类引用调用哪个Print()取决于r引用了哪个类的对象
}

int main()
{
    A a;
    B b;
    PrintInfo(a); // 使用基类对象,调用基类中的函数,输出A::Print
    PrintInfo(b); // 使用基类对象,调用派生类中的函数,输出B::Print
    return 0;
}

【Code Explanation】

  • Defines a base class  A and a public derived class B ​​​​​​​​.

  • Among them, there is a virtual function A in  Print().

  • Defines a function PrintInfo() named ​​​​​​​​​​, whose parameter is A a reference to the base class​​​​​​​​​​.

  • In main() the function , an A object of type a and an B object of type are created respectively. b ​.

  • Then, use PrintInfo() the​​​​​​​​​functions to call Print() the​​​​​​​​​​​methods of these two objects.

  • Since Print() the method is a virtual function, it actually calls the virtual function corresponding to the actual type of the object.

  • Therefore, PrintInfo(a) in ​​​​​​​​​​​​, although the parameter is a reference to the base class, but because the reference actually points to A an object of the​​​​​​​​​​​​​class a , so output "A ::Print" .

  • PrintInfo(b) In ​​​​​​​​​​, the parameter is a reference to the base class, but since the reference points to B an object of the b​​​​​​​​​​​​​​​​​​​​​​, so output "B::Print".

  • This demonstrates the properties of base class references and polymorphism, an important concept in C++ object-oriented programming.

【Results of the】

  • In the program, the base class A and B. ​​​​​​​​​​​​​​​​​​​​​​There  is aA virtual Print()function B​​​​​​​​​​​​​​​​​​​​​​​​VirtualPrint() functions.
  • Then a function is defined PrintInfo()​​​​​​​​​​​​​​​​​​​​, the parameter of this function is A the reference of the base class .
  • In main() the function , a base class a object is created. .bPrintInfo()
  • Since the parameter PrintInfo() of is Aa reference to the base class, Print() and the ​​​​​​The function is a virtual function, so the base class A and  A::Print"​​​​​​​​​​​​​​​​​​​​​​"B::Print".BPrint()
  • This shows the characteristics of polymorphism, which is an important feature of C++ object-oriented programming.
A::Print
B::Print

(5) * The principle of polymorphism

  • The key to polymorphism is that when a virtual function is called through a base class pointer or reference, the compilation phase cannot determine whether the function of the base class or the derived class is called, and it can only be determined at runtime.
  • The storage space occupied by the derived class object is equal to the storage space occupied by the member variables of the base class plus the storage space occupied by the member variables of the derived class object itself.


Two, polymorphic instance

  • Define a base class CShape to represent general graphics, and then derive three subclasses to represent rectangle class, circle class and triangle class respectively.
  • The base class defines virtual functions for calculating the area of ​​graphics and outputting information. The three derived classes all inherit these two virtual functions, and can calculate the respective areas of specific graphics and output the results.

[Example] Class inheritance, polymorphic implementation

【Example code】 Examples of inheritance and polymorphism, using display  functions of pointers and references respectively:

#include <iostream>
using namespace std;

const double PI = 3.14159; // 圆周率

class Point
{
private:
    double x, y; // 坐标点
public:
    Point(double i, double j) { x = i; y = j; } // 构造函数
    virtual double area() { return 0; } // 计算面积,虚函数
};

class Circle : public Point // 圆类继承自点类
{
private:
    double radius; // 半径
public:
    Circle(double a, double b, double r) : Point(a, b) { radius = r; } // 构造函数
    double area() { return PI * radius * radius; } // 计算面积,虚函数
};

void display(Point* p) { cout << p->area() << endl; } // 显示面积,通过指针调用虚函数
void display(Point& a) { cout << a.area() << endl; } // 显示面积,通过引用调用虚函数

int main()
{
    Point a(1.5, 6.7); // 定义点对象
    Circle c(1.5, 6.7, 2.5); // 定义圆对象
    Point* p = &c; // 派生类对象的地址赋给基类指针
    Point& rc = c; // 派生类对象初始化基类引用
    display(a); // 基类对象调用基类虚函数area,输出0
    display(p); // 指针调用派生类虚函数area,输出19.6349
    display(rc); // 引用调用派生类虚函数area,输出19.6349
    return 0;
}

【Code Explanation】

  • This code is a simple example of inheritance and polymorphism, which defines a base class Point and a derived class Circle.

  • The Point class has two private member variables x and y. The member function Point(double i, double j) represents the constructor of this class. The virtual function double area() is used to calculate the area, and the return value is 0.

  • The Circle class inherits from the Point class, and has a private member variable radius, indicating the radius of the circle.

  • The member function Circle(double a, double b, double r) is the constructor of the Circle class, and calls the constructor of the Point class to initialize x and y of the base class.

  • The overloaded function double area() is used to calculate the area, and the return value is the area of ​​the circle.

  • A Point object a and a Circle object c derived from the Point object are defined in the main function, and a base class pointer p and base class reference rc are respectively defined in two ways. p By assigning the address of the derived class object c to the base class pointer, the function of pointing a pointer to the base class to a derived class object is realized, that is, to name the Circle object as a Point object, which is a kind of polymorphism reflect.

  • There are two overloaded versions of the display function. The parameters are the pointer and reference of Point respectively, and both use the virtual function pointer to call the virtual function area() of the object.

  • By calling the display function, the area of ​​the three objects (Point object, derived Point object pointer and reference) is output. From the output results, it can be seen that the base class pointer and base class reference call the virtual function of the derived class, realizing multiple attitude.

  • At the end of the program, a return value of 0 terminates the program.

【Results of the】

  1. Call display(a), output 0. At this time, the p pointer and rc reference point to different objects, p points to the derived object of c, and rc points to the same pointer. Therefore, by passing in the parameter a, directly call the area() function of the Point class, and output 0.

  2. Calling display§, outputs 19.6349. At this time, p points to the object of the Circle class, and the Circle class rewrites the area() function in the Point class, so the area() function in the Circle class is executed when calling, and the output is the calculated value of the area of ​​​​the circle.

  3. Call display(rc), which outputs 19.6349. Same as display§, the rc reference points to the object in the Circle class, so the calculated value of the area of ​​the circle is output.

  4. Therefore, calling virtual functions through pointers and references achieves the effect of polymorphism:

0
19.6349
19.6349


3. The use of polymorphism

Calling other virtual member functions in ordinary member functions ( except static member functions, constructors, and destructors ) is also allowed and polymorphic.
  • Called in the constructor, the compilation system can decide which version of the class to call based on this, so it is not polymorphic;
  • Called in the destructor, so it is not polymorphic;
  • When implementing polymorphism, the conditions that must be met are: use the base class pointer or reference to call the virtual function declared in the base class.
  • The virtual function inherited from the base class in the derived class can write the virtual keyword, or omit this keyword, which does not affect the function in the derived class is also a virtual function. 

[Example 1] Class inheritance and polymorphism

[Sample Code] Demonstrates the inheritance and polymorphism of classes in C++ . The main way to realize polymorphism in C++ is through virtual functions and base class pointers or references. You need to declare a virtual function in the base class and override the virtual function in the derived class. At runtime, the program will dynamically bind and call according to the actual object type to achieve the effect of polymorphism:

#include <iostream>

using namespace std;

class CBase // 基类
{
public:
    void func1() // 不是虚函数
    {
        cout << "CBase::func1()" << endl;
        func2(); // 在成员函数中调用虚函数
        func3();
    }
    virtual void func2() // 虚函数
    {
        cout << "CBase::func2()" << endl;
    }
    void func3() // 不是虚函数
    {
        cout << "CBase::func3()" << endl;
    }
};

class CDerived: public CBase // 派生类
{
public:
    virtual void func2() override // 虚函数
    {
        cout << "CDerived:func2()" << endl;
    }
    void func3()
    {
        cout << "CDerived:func3()" << endl;
    }
};

int main()
{
    CDerived d;
    d.func1(); // 调用的基类中的成员函数
    return 0;
}

【Code Explanation】

  1. Define the base class: first define a base class  CBase, which defines three member functions  func1(), func2() and func3(). Among them, func1() the function is a non-virtual function, in which the virtual function of the base class func2() and the non-virtual function are called func3(). func2() The function is a virtual function that outputs "CBase::func2()".
    class CBase // 基类
    {
    public:
        void func1() // 不是虚函数
        {
            cout << "CBase::func1()" << endl;
            func2(); // 在成员函数中调用虚函数
            func3();
        }
        virtual void func2() // 虚函数
        {
            cout << "CBase::func2()" << endl;
        }
        void func3() // 不是虚函数
        {
            cout << "CBase::func3()" << endl;
        }
    };
  2. Defining a derived class: A derived class is defined on CBase the basis of CDerived​​​​​​​​​​​​, in which the virtual function of the base class is rewritten func2(), and "CDerived:func2()" is output.
    class CDerived: public CBase // 派生类
    {
    public:
        virtual void func2() override // 虚函数
        {
            cout << "CDerived:func2()" << endl;
        }
        void func3()
        {
            cout << "CDerived:func3()" << endl;
        }
    };
  3. Call member function: in main() the function , through the derived CDerived class member function.ddfunc1()
    int main()
    {
        CDerived d;
        d.func1(); // 调用的基类中的成员函数
        return 0;
    }

【Results of the】

  • In the program, firstly create an  CDerived instantiated object of a derived class d​​​​​​​​​​​​, and then call​​​​​​​​​​​for d.func1() function execution.
  • During execution, it is output first  CBase::func1().
  • In func1() ​​​​​​​​​​calling a virtual function func2()​​​​​​​​​, because  func2()it is a virtual function, the program will automatically choose to call the implementation in the derived class, that is, output  CDerived:func2().
  • Then, func1() call the non-virtual function func3()​​​​​​​​​​,  output CBase::func3().
  • So the final result is to output CBase::func1()​​​​​​​​​​, CDerived:func2() and  CBase::func3().
CBase::func1()
CDerived:func2()
CBase::func3()

[Example 2] Virtual functions, inheritance and polymorphism

[Sample code] mainly introduces virtual functions, inheritance and polymorphism, mainly demonstrates the inheritance relationship and polymorphism among the three classes, and demonstrates the function execution sequence and function calling method between different derived classes:

#include <iostream>
using namespace std;

class A // 基类
{
public:
    virtual void hello() // 虚函数
    {
        cout << "A::hello" << endl;
    } 
    virtual void bye() // 虚函数
    {
        cout << "A::bye" << endl;
    } 
};

class B: public A // 派生类
{
public:
    virtual void hello() // 虚函数
    { 
        cout << "B::hello" << endl; 
    }
    B()
    {
        hello(); // 调用虚函数,但不是多态
    }
    ~B()
    {
        bye(); // 调用虚函数,但不是多态
    }
};

class C: public B // 派生类
{
public:
    virtual void hello() // 虚函数
    {
        cout << "C::hello" << endl;
    }
};

int main()
{
    C obj;
    return 0;
}

【Code Explanation】

  1. Define the base class: Define a base class A, which defines two virtual functions hello and bye. A virtual function is a function declared with the virtual keyword and curly braces {}, and derived classes can override them as needed.
    class A // 基类
    {
    public:
        virtual void hello() // 虚函数
        {
            cout << "A::hello" << endl;
        } 
        virtual void bye() // 虚函数
        {
            cout << "A::bye" << endl;
        } 
    };
  2. Define the first derived class: On the basis of the base class A, define a derived class B, in which the virtual function hello is rewritten, and B::hello is output. The hello function is called in B's constructor, but polymorphism cannot be used because the object has not been fully constructed at this point. The bye function is called in the destructor of B, and the use of virtual functions in the destructor has no polymorphic effect.
    class B: public A // 派生类
    {
    public:
        virtual void hello() // 虚函数
        { 
            cout << "B::hello" << endl; 
        }
        B()
        {
            hello(); // 调用虚函数,但不是多态
        }
        ~B()
        {
            bye(); // 调用虚函数,但不是多态
        }
    };
  3. Define the second derived class: On the basis of derived class B, another derived class C is defined, in which the virtual function hello is rewritten.
    class C: public B // 派生类
    {
    public:
        virtual void hello() // 虚函数
        {
            cout << "C::hello" << endl;
        }
    };
  4. Main function: In the main function, an object obj of class C is created. In the creation process, create the base class first, then derive the B class object, and finally derive the C class object. When a derived class object is created, the execution order of the constructor of the class is base class -> derived class B -> derived class C. Therefore, when creating an object of class C, the constructor of class A is called first, then the hello function is called in the constructor of class B, and B::hello is output, and finally the construction of class C is completed.
    int main()
    {
        C obj;
        return 0;
    }
  5. After the program is executed, the memory is released automatically, and the order of destruction is opposite to that of construction, that is, the destructors of derived classes C and B are executed first, and then the destructors of class A are executed.
  6. In short, the code mainly introduces virtual functions, inheritance and polymorphism, and demonstrates the function execution sequence and function calling method between different derived classes. Due to the particularity of derived class constructors and destructors, it is recommended to avoid using virtual functions in this case to avoid unnecessary problems.

【Results of the】

  • During program execution, an object obj of class C is created first, and class C inherits class B, and class B inherits class A.
  • In the process of creating obj object, the constructor is executed in the order of base class -> derived class B -> derived class C. The hello() function is called in the constructor of class B, and B::hello is output. Since the object has not been fully constructed at this point, polymorphism cannot be used.
  • The main function does not call any function, so after the main function is executed, the program enters the destructor stage, and the destructor is executed in the order of derived class C -> derived class B -> base class A. The bye() function is called in the destructor of class B, but since polymorphism cannot be used as well, A::bye is output.
  • Because the hello() and bye() functions in class B do not use polymorphism, neither function call is polymorphic when creating an instantiated object of class B.
B::hello
A::bye

[Example 3] Polymorphic mechanism

[Example code] An example based on the polymorphic mechanism in C++ is implemented:

#include<iostream>
using namespace std;

// 定义类 A 
class A 
{
public:
    // 在类 A 中定义 func1() 函数
    void func1() 
    { 
        cout << "A::func1" << endl; 
    } 
    // 在类 A 中定义 func2() 函数并将其设置为虚函数
    virtual void func2() 
    {
        cout << "A::func2" << endl; 
    } 
};

// 定义类 B,继承自类 A
class B : public A 
{
public:
    // 在类 B 中重新定义 func1() 函数并将其设置为虚函数
    virtual void func1() 
    {
        cout << "B::func1" << endl;
    }
    // 在类 B 中重新定义 func2() 函数并将其设置为虚函数
    void func2() // func2 自动成为虚函数
    {
        cout << "B::func2" << endl;
    }
};

// 定义类 C,继承自类 B
class C : public B //类 C 以类 A 为间接基类
{
public:
    // 在类 C 中重新定义 func1() 函数并将其设置为虚函数
    void func1() // func1 自动成为虚函数
    {
        cout << "C::func1" << endl;
    }
    // 在类 C 中重新定义 func2() 函数并将其设置为虚函数
    void func2() // func2 自动成为虚函数
    {
        cout << "C::func2" << endl;
    }
};

// 程序入口函数
int main()
{
    // 创建 C 类的对象 obj
    C obj;
    // 使用 A 类型的指针指向 obj 对象
    A* pa=&obj;
    // 使用 B 类型的指针指向 obj 对象
    B* pb=&obj;
    // 通过调用 pa 所指向对象的 func2() 函数,触发多态
    pa->func2(); // 多态
    // 直接调用 pa 所指向对象的 func1() 函数,因为 func1() 不是虚函数,所以不会发生多态
    pa->func1(); // 不是多态
    // 通过调用 pb 所指向对象的 func1() 函数,触发多态
    pb->func1(); // 多态
    return 0;
}

【Code Explanation】

  1. #include<iostream>: Introduce the standard input and output library iostream in C++.

  2. using namespace std;: Use the std namespace.

  3. class A: A class A is defined.

  4. class B : public A: Defines a class B that inherits from class A.

  5. class C : public B: Defines a class C that inherits from class B.

  6. void func1(): A function called func1() is defined in classes A, B, and C, which are respectively overloaded and virtual functions are specified.

  7. virtual void func2(): Classes A, B, and C all define a function named func2(), and declare it as a virtual function. Among them, void func2() can be overloaded, and if you want to use the function of the same name in the base class, you can add keywords,  virtualwhich will determine the identity at runtime, thereby achieving polymorphism, which is also a common implementation method of polymorphism in C++ .

  8. pa->func2();: Calling the virtual function func2() with a pointer to an object of class A triggers polymorphism because the type of the pointed object is determined at runtime.

  9. pa->func1();: Calling the non-virtual function func1() with a pointer to an object of class A cannot trigger polymorphism.

  10. pb->func1();: Calling the virtual function func1() with a pointer to an object of class B triggers polymorphism because the type of the pointed object is determined at runtime.

【Results of the】

  • C::func2 : Indicates that a polymorphic function is called using a pointer to an object of class C  func2(), which triggers the polymorphic mechanism because the function is overloaded in class C;
  • A::func1 : Indicates that a non-virtual function is called using a pointer to an object of class A  func1(), and polymorphism cannot be triggered;
  • B::func1 : Indicates that a polymorphic function is called using a pointer to an object of class B  func1(), which triggers the polymorphic mechanism because the function is overloaded in class B:
C::func2
A::func1
B::func1


4. Virtual destructor

  • If the object pointed to by a base class pointer is a derived class object dynamically generated by the new operator, when releasing the space occupied by the object, if only the destructor of the base class is called, only the destructor in the destructor will be completed. The release of space does not involve the release of space in the destructor of the derived class, which is easy to cause memory leaks.
  • The general format for declaring a virtual destructor is as follows:
    virtual 〜类名( );
  • A virtual destructor has no return type and no parameters, so its format is very simple.
  • A virtual destructor has no return type and no parameters, so its format is very simple.
  • If the destructor of a class is virtual, the destructors of all subclasses derived from it are also virtual. The purpose of using a virtual destructor is to achieve polymorphism when the object dies.
  • It can be seen that not only the destructor of the base class is called this time, but also the destructor of the derived class is called .
  • As long as the destructor of the base class is a virtual function, the destructor of the derived class will automatically become a virtual destructor regardless of whether it is declared with the virtual keyword .
  • Generally speaking, if a class defines a virtual function, it is best to define the destructor as a virtual function.
  • But remember, the constructor cannot be a virtual function . 

[Example 1] Virtual destructor

[Sample code] mainly shows a base class  ABase and a derived class  Derived, Derived inherited from  ABase. In  main the function, use a base class pointer to point to the derived class object created by new, and release the pointer after use:

#include<iostream>
using namespace std;     // 命名空间

class ABase {
public:
    ABase() {            // 基类构造函数
        cout << "ABase构造函数" << endl;
    }
    ~ABase() {           // 基类析构函数
        cout << "ABase::析构函数" << endl;
    }
};

class Derived: public ABase {   // 继承自基类
public:
    int w, h;      // 成员变量
    Derived() {    // 派生类构造函数
        cout << "Derived构造函数" << endl;
        w = 4;
        h = 7;
    }
    ~Derived() {  // 派生类析构函数
        cout << "Derived::析构函数" << endl;
    }
};

int main() {
    ABase* p = new Derived();   // 使用基类指针指向 new 创建的派生类对象
    delete p;                   // 删除基类指针
    return 0;
}

【Code Explanation】

  1. First, a derived class object is created  Derived and memory is allocated.
  2. Next, ABase the constructor of the base class is called, and "ABase constructor" is output.
  3. Then, Derived the constructor of the derived class is called, and "Derived constructor" is output.
  4. After running, call the destructor  ~Derived() and  ~ABase(), and output "Derived::destructor" and "ABase::destructor" respectively.
  5. Finally free the memory.

【pay attention】

  • Since the base class  ABase 's destructor is virtual, the derived class's destructor is called instead of the base class's destructor when the pointer is freed.
  • Here, the control of the base class to the derived class is realized by using the base class pointer to bind the derived class object.
  • ABase It should be noted that the destructor of the base class here is a virtual function, and the destructor of the subclass should also be a virtual function (the C++11 specification has already defined, and the destructor is also a virtual function by default), so as to ensure  Call the correct destructor when freeing memory.

【Results of the】

  • First create an  Derived object of a derived class and allocate memory;
  • Then output  ABase the constructor of the base class and  Derived the constructor of the derived class;
  • Then use the base class pointer to point to the derived class object created by new and operate;
  • Finally the memory is freed by deleting the base class pointer.
  • Derived When the program ends, the destructor of the derived  class is called first  , and then ABase the destructor of the base class is called.
ABase构造函数
Derived构造函数
Derived::析构函数
ABase::析构函数

[Example 2] Virtual destructor

[Sample code] The program defines a base class  ABase and a derived class  Derived, which  Derived inherit from the base class  ABase. In  main the function, a derived class object is created, a base class pointer is used to point to the derived class object, and then deleted:

#include <iostream>

using namespace std;

class ABase {
public:
    ABase() {            // 基类构造函数
        cout << "ABase构造函数" << endl;
    }
    virtual ~ABase() {   // 基类析构函数(虚函数) ,与上一个示例代码唯一区别的代码行
        cout << "ABase::析构函数" << endl;
    }
};

class Derived: public ABase {   // 派生类,继承自基类
public:
    int w, h;      // 成员变量
    Derived() {    // 派生类构造函数
        cout << "Derived构造函数" << endl;
        w = 4;
        h = 7;
    }
    ~Derived() {  // 派生类析构函数
        cout << "Derived::析构函数" << endl;
    }
};

int main() {
    ABase* p = new Derived();   // 使用基类指针指向 new 创建的派生类对象
    delete p;                   // 删除基类指针
    return 0;
}

【Code Explanation】

  1. #include <iostream>: Contains the input and output stream library  iostream.
  2. using namespace std: Use a namespace  std.
  3. class ABase: Declare a base class  ABase.
  4. public: Public member functions and member variables.
  5. ABase(): The constructor of the base class.
  6. cout: output "ABase Constructor".
  7. virtual ~ABase(): The virtual destructor of the base class.
  8. cout: output "ABase::destructor".
  9. class Derived: Declare a derived class  Derived.
  10. public: Public member functions and member variables.
  11. int w, h: Member variables  w and  h define integer types.
  12. Derived(): The constructor of the derived class.
  13. cout: output "Derived constructor".
  14. w=4; h=7: Assign initial values ​​to member variables  w and  h .
  15. ~Derived(): The destructor of the derived class.
  16. cout: output "Derived::Destructor".
  17. ABase* p = new Derived(): Use the base class pointer to point to the derived class object created by new.
  18. delete p: Delete the base class pointer and execute the destructor.
  19. return 0: The program ends and a successful signal is returned.
  20. Since  ABase the destructor is a virtual function,  the destructor of ABase the derived class will be called automatically when the pointer of the type  Derived is deleted; here, the base class pointer is used to point to the derived class object, and the subclass polymorphism is realized from the perspective of runtime.

【Results of the】

  • The program first creates an  Derived object of the derived class and stores its address in the base class pointer  p .
  • Then print out "ABase constructor" and "Derived constructor".
  • Then the object is deleted by calling  delete , and the destructor of the derived class and the destructor of the base class are called in turn.
  • Since the destructor of the base class is virtual,  Derived the destructor of the derived class is called.
ABase构造函数
Derived构造函数
Derived::析构函数
ABase::析构函数


5. Pure virtual functions and abstract classes

(1) Pure virtual function 

  • The role of the pure virtual function is equivalent to a unified interface form, indicating that there should be such an operation in each derived class of the base class, and then concretely implement the operations related to this derived class in each derived class.
  • A pure virtual function is a virtual function declared in the base class without a specific definition, and each derived class gives its own definition according to actual needs.
  • The general format for declaring a pure virtual function is as follows: For example: virtual void fun( )=0;
    virtual 函数类型 函数名(参数表)=0;
  • A pure virtual function has no function body, and "=0" must be written after the parameter list. Derived classes must override this function.
  • When called according to the pure virtual function name, the statement rewritten in the derived class is executed, that is, the version in the derived class is called.

(2) Abstract class 

  • A class that contains pure virtual functions is called an abstract class .
  • Because the abstract class has unfinished function definitions, it cannot instantiate an object.
  • In the derived class of an abstract class, if the definition of all pure virtual functions is not given, the derived class continues to be an abstract class.
  • Until all pure virtual function definitions are given in the derived class, it is no longer an abstract class, and an object can only be instantiated.
  • Although objects of abstract classes cannot be created, pointers and references of abstract classes can be defined.
  • Such pointers and references can point to and access members of derived classes, and this access is polymorphic.
A pure virtual function is different from a virtual function whose body is empty.
⚫Their differences are as follows:
  • A pure virtual function has no function body, and an empty virtual function has an empty function body.
  • The class where the pure virtual function is located is an abstract class and cannot be instantiated directly;
  • The class where the empty virtual function is located can be instantiated.
⚫Their common features are:
  • Both the pure virtual function and the virtual function whose function body is empty can derive a new class, and then give the realization of the virtual function in the new class, and this new realization has polymorphic characteristics.

[Example] Abstract class

[Sample code] This program defines an abstract class  A and a  A class  inherited from it B. A There is a pure virtual function in  print(), which means that  A the pointer of the type can point to  B the object of the class. In  main the function, use the derived class object through a base class pointer, and  print() demonstrate polymorphism by calling the method:

#include <iostream>
using namespace std;

class A {
private:
    int a;
public:
    virtual void print() = 0;  // 纯虚函数,抽象类
    void func1() {
        cout << "func1" << endl;
    }
};

class B : public A {
public:
    void print();
    void func1() {
        cout << "B_func1" << endl;
    }
};

void B::print() {
    cout << "B_Print" << endl;
}

int main() {
    // A a; // 错误,抽象类不能实例化
    // A *p = new A; // 错误,不能创建类 A 的实例
    // A b[2]; // 错误,不能声明抽象类的数组
    A* pa; // 正确,可以声明抽象类的指针
    A* pb = new B; // 使用基类指针指向派生类对象
    pb->print(); // 调用的是类 B 中的函数,多态,输出 B_Print
    B b;
    A* pc = &b;
    pc->func1(); // 因为不是虚函数,调用的是类 A 中的函数,输出 func1
    return 0;
}

【Code Explanation】

  1. #include <iostream>: Contains the input and output stream library  iostream.
  2. using namespace std: Use a namespace  std.
  3. class A: Declare an abstract class  A.
  4. private: Private member functions and member variables.
  5. int a: The member variable  a defines an integer type.
  6. virtual void print() = 0: Pure virtual function, abstract class.
  7. void func1(): member function.
  8. cout: output "func1".
  9. class B : public A: Declare a class  Bthat inherits from  A.
  10. void print(): Function overloading.
  11. cout: Output "B_Print".
  12. void func1(): Function overloading.
  13. cout: output "B_func1".
  14. int main(): Program entry.
  15. A* pa: Declare a base class pointer  pathat can point to  A an object of an abstract class.
  16. A* pb = new B: Defines a derived class object using a pointer to the base class.
  17. pb->print(): Use the base class pointer to call  B the overloaded function in  the class print(), output "B_Print".
  18. B b: Creates an  B object of  a derived class b.
  19. A* pc = &b: Use the base class pointer  pc to point to the derived class object  b.
  20. pc->func1()A : Call a function in  a class using a base class pointer  func1(), output "func1".
  21. return 0: The program ends and a successful signal is returned.

【Results of the】

  • This code defines the class  A and its derived classes  B. A It is an abstract class that contains a pure virtual function  print(). The function declared as a pure virtual function has no default implementation and needs to be implemented in the derived class.
  • B A method is exposed  print() and implemented to output "B_Print".
  • B Another member method of  func1() is also defined to output "B_func1".
  • A In the main function, it is wrong to  try to instantiate the abstract class  and its array, but you can use A* the pointer of the type to point to  B the object of the derived class, because the polymorphic mechanism of C++ allows the base class pointer to point to the derived class object, so that you can Implement dynamic binding, that is, call methods based on the actual type of the object.
  • Here, A* pb = new B; a pointer is instantiated  pb, pointing to  B an object of a derived class.
  • pb->print();A The pure virtual function is called through the pointer of   the base class  print(). Since  the method B is implemented  in the method, the method  in  print() the class is actually called   , and "B_Print" is output.Bprint()
  • B Next, an object  is instantiated  b, its address is assigned to  A a pointer to the type  pc, and then called  pc->func1(). Although  func1() the function is a non-virtual function, it does not depend on the object type, but because  the function B is redefined in  func1() , it will be called here  B . function  func1() , output "B_func1".
B_Print
B_func1

(3) Virtual base class

[Format] The general format for defining a virtual base class is as follows:
class 派生类名:virtual 派生方式 基类名
{
    派生类体
};

[Explanation] In order to avoid ambiguity, C++ provides a virtual base class mechanism , so that in derived classes, members inheriting the same indirect base class only retain one version.

[Example] The inheritance relationship of various types as shown in the figure is as follows:

class A
class B : virtual public A
class C : virtual public A
class D : public B, public C

 

[Example] Virtual base class

【Example code】

#include <iostream>
using namespace std;

class A {
public:
    int a;
    void showa() {
        cout << "a=" << a << endl;
    } 
};

class B : virtual public A {  // 对类 A 进行了虚继承
public:
    int b;
}; 

class C : virtual public A {  // 对类 A 进行了虚继承
public:
    int c;
}; 

class D : public B, public C {  // 派生类 D 的两个基类 B、C 具有共同的基类 A
                                 // 采用了虚继承,从而使类 D 的对象中只包含着类 A 的1个实例
public:
    int d;
}; 

int main() {
    D Dobj;  // 说明派生类 D 的对象
    Dobj.a = 11;  // 若不是虚继承,此行会出错!因为 "D::a" 具有二义性
    Dobj.b = 22;
    Dobj.showa();  // 输出 a=11
                   // 若不是虚继承,此行会出错!因为 "D::showa" 具有二义性
    cout << "Dobj.b=" << Dobj.b << endl;  // 输出 Dobj.b=22
}

【Code Explanation】

  • This code defines 4 classes: the base class  A, and 3 derived classes: B, C and  D. B and  C both use virtual inheritance  A, D inheritance  B and  C. This virtual inheritance method can avoid the problem of "ambiguity", that is, there are multiple base classes in the derived class that contain the same member variables or functions, which brings ambiguity, and it is impossible to determine which member of the base class to use.
  • In  the function, an  object  main() is created  , referred to here as the root or top-level object. Because   it is multiple inheritance, it is necessary to construct the base class first, and then construct the derived class when constructing. Specifically, first construct the virtual base class  , then construct   and  respectively , and finally construct  . This ensures that  there is only one  child object  in the object  . If virtual inheritance is not used, then   the   copy   inherited from each class  will result in  two   sub-objects in the object, duplication and waste of space.DDobjDABCDDABCADA
  • Then set  the value of a and  b , here directly access  the member variable  and  D of the object  , although there are multiple paths to reach the member variable  , but because of the use of virtual inheritance, there is only one copy of the variable  , so there is no ambiguity problem. Similarly,   since the value   output by calling the method  is a virtual function, there is only one  method, and there will be no ambiguity. The last output   value.abaashowa()Dobj.ashowa()Dobj.b
  • It should be noted that   there may be ambiguity or ambiguity when  D directly accessing member variables or member functions of the base class  in the derived class, and it must be clear which  sub-object is specified. Using virtual inheritance can effectively solve this problem.AA

【Results of the】

a=11
Dobj.b=22

Guess you like

Origin blog.csdn.net/qq_39720249/article/details/131386361