Summary of virtual functions and polymorphism + virtual destructor knowledge points Summary of C++ programming and algorithm notes (5) Guo Wei, Peking University

Virtual functions and polymorphism

virtual function

In C++, a virtual function (Virtual Function) is a special function used in the base class. After it is declared as a virtual function in the base class, it can also be redefined in the derived class. The virtual function implements the polymorphic feature, and can access the same-named function in the derived class through the base class pointer or reference and dynamic binding.
The definition format of a virtual function is as follows:

class Base{
public:
    virtual void func() {
        // function body
    }
};

In the above code, func()the function is declared as a virtual function. In derived classes, this function can be redefined to achieve polymorphism.
When calling a virtual function with a base class pointer or reference, the program will determine the type of the object pointed to by the current pointer or reference at runtime, and then dynamically bind the calling address of the function. Therefore, when a pointer or reference points to a derived class object, the function in the derived class is called.
Here is a simple example that demonstrates the usage of virtual functions:

#include <iostream>
using namespace std;
class Shape {
public:
    virtual float area() {
        cout << "Parent class area :" << endl;
        return 0;
    }
};
class Rectangle : public Shape {
public:
    float area() {
        cout << "Rectangle class area :" << endl;
        return (width * height);
    }
private:
    int width;
    int height;
public:
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
};
class Triangle : public Shape {
public:
    float area() {
        cout << "Triangle class area :" << endl;
        return (0.5 * base * height);
    }
private:
    int base;
    int height;
public:
    Triangle(int b, int h) {
        base = b;
        height = h;
    }
};
int main() {
    Shape* shape;
    Rectangle rec(10, 7);
    Triangle tri(10, 5);
    shape = &rec;
    shape->area();
    shape = &tri;
    shape->area();
}

In the above code, Shapeis the base class, Rectangleand Triangleis the derived class. The functionsShape in are declared virtual, so they can be overloaded in derived classes. area()In main()the function , a Shapepointer variable of type is declared shape, through which the function with the same name in the derived class is accessed. When shapepoints to Rectanglethe object, the function in is Rectanglecalled area(); when shapeit points to Trianglethe object, the function Trianglein area().

In the definition of a class, the member function preceded by the virtual keyword is a virtual function.

class base {
    
    
virtual int get() ;
};
int base::get() 
{
    
     }virtual 关键字只用在类定义里的函数声明中,

When writing the function body, it is not necessary to

polymorphic representation

In object-oriented programming, polymorphism (Polymorphism) means that the same function or method can accept different types of parameters or return different types of results. Polymorphism is one of the three major characteristics of object-oriented programming (encapsulation, inheritance, polymorphism).
In C++, there are two main forms of polymorphism:

  1. Function overloading
    In C++, function overloading (Function Overloading) is also a form of polymorphism. The same function name can be used for functions with multiple parameter types or different numbers of parameters, and the compiler will determine which function to call based on the parameter types and numbers of the function. For example:
void add(int a, int b) {
    cout << "调用的是int类型加法函数:" << a + b << endl;
}
void add(double a, double b) {
    cout << "调用的是double类型加法函数:" << a + b << endl;
}
int main() {
    add(1, 2); // 调用的是int类型加法函数:3
    add(1.5, 2.6); // 调用的是double类型加法函数:4.1
    return 0;
}

In the above code, add()the function is overloaded twice, intfor doubleparameters of type and . In main()the function , according to the type of the parameter passed in, the compiler will automatically select and call the corresponding function.
2. Virtual functions
The concept and usage of virtual functions have been introduced earlier, and virtual functions realize runtime polymorphism. Access the function with the same name in the derived class through the base class pointer or reference and dynamic binding.
For example:

#include <iostream>
using namespace std;
class Shape {
public:
    virtual float area() {
        cout << "Parent class area :" << endl;
        return 0;
    }
};
class Rectangle : public Shape {
public:
    float area() {
        cout << "Rectangle class area :" << endl;
        return (width * height);
    }
private:
    int width;
    int height;
public:
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
};
class Triangle : public Shape {
public:
    float area() {
        cout << "Triangle class area :" << endl;
        return (0.5 * base * height);
    }
private:
    int base;
    int height;
public:
    Triangle(int b, int h) {
        base = b;
        height = h;
    }
};
int main() {
    Shape* shape;
    Rectangle rec(10, 7);
    Triangle tri(10, 5);
    shape = &rec;
    shape->area();
    shape = &tri;
    shape->area();
}

In the above code, Shapeis the base class, Rectangleand Triangleis the derived class. The functionsShape in are declared virtual, so they can be overloaded in derived classes. area()In main()the function , a Shapepointer variable of type is declared shape, through which the function with the same name in the derived class is accessed. When shapepoints to Rectanglethe object, the function in is Rectanglecalled area(); when shapeit points to Trianglethe object, the function Trianglein area(). This is the performance of runtime polymorphism.

Objects of derived classes can be assigned base class references

When calling a virtual function with the same name in both the base class and the derived class through a base class reference:

(1) If the reference refers to an object of a base class, then the virtual function of the base class is called;

(2) If the reference refers to an object of a derived class, then the virtual function of the derived class is called. This mechanism is also called "polymorphism".

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-p8d9yz08-1687264779367)(2023-06-20-20-11-19.png)]

The role of polymorphism

The use of polymorphism in object-oriented programming can enhance the scalability of the program, that is, when the program needs to be modified or added functions, less code needs to be changed and added.

Example of game program using polymorphism

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-ZqkStrLm-1687264779368)(2023-06-20-20-13-01.png)] [External link
picture The transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-FdVlSDLM-1687264779369)(2023-06-20-20-13-30.png)]

The basic idea:

Write Attack, FightBack, and Hurted member functions for each monster class.

The Attack function expresses the attack action, attacks a certain monster, and calls the attacked monster's

The Hurted function is used to reduce the life value of the attacked monster, and at the same time call the FightBack member function of the attacked monster to suffer the counterattack of the attacked monster.

The Hurted function reduces its own health and performs injury actions.

The FightBack member function represents the counterattack action, and calls the Hurted member function of the countered object
to make the countered object injured.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-lFtMMvOU-1687264779369)(2023-06-20-20-14-16.png)]

Non-polymorphic implementation method

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-yQQ01rGL-1687264779369)(2023-06-20-20-14-39.png)]

code example

The following is an example of a game program using polymorphism. This program simulates a simple RPG game, which contains different types of characters, each of which has its own attack and defense. The specific implementation is as follows:

#include <iostream>
#include <string>
using namespace std;
// 角色基类
class Character {
protected:
    string name;
    int attack;
    int defense;
public:
    Character(string name, int attack, int defense) {
        this->name = name;
        this->attack = attack;
        this->defense = defense;
    }
    virtual int getAttack() { return attack; } // 获取攻击力
    virtual int getDefense() { return defense; } // 获取防御力
    virtual void attackTarget(Character* target) {} // 攻击目标
};
// 具体角色类:战士
class Warrior : public Character {
public:
    Warrior(string name, int attack, int defense) : Character(name, attack, defense) {}
    int getAttack() { return attack * 2; } // 攻击力加倍
    void attackTarget(Character* target) {
        int damage = getAttack() - target->getDefense();
        damage = max(damage, 0);
        cout << name << " 对 " << target->name << " 造成了 " << damage << " 点伤害!" << endl;
    }
};
// 具体角色类:法师
class Mage : public Character {
public:
    Mage(string name, int attack, int defense) : Character(name, attack, defense) {}
    int getDefense() { return defense / 2; } // 防御力减半
    void attackTarget(Character* target) {
        int damage = getAttack() - target->getDefense();
        damage = max(damage, 0);
        cout << name << " 对 " << target->name << " 造成了 " << damage << " 点伤害!" << endl;
    }
};
// 游戏主程序
int main() {
    Warrior warrior("战士", 50, 30);
    Mage mage("法师", 40, 40);
    Character* player = &warrior;
    Character* enemy = &mage;
    cout << player->name << " 攻击 " << enemy->name << ":" << endl;
    player->attackTarget(enemy);
    cout << enemy->name << " 攻击 " << player->name << ":" << endl;
    enemy->attackTarget(player);
    return 0;
}

In the above code, the basic attributes and behaviors of the character are defined Characterin , where getAttack()the and getDefense()functions are virtual functions, which obtain the attack power and defense power of the character respectively. The specific role classes Warriorand Mageare inherited from the role base class Character, and the virtual functions getAttack()and getDefense(), and define the specific behavior of the attack target.

In the main program of the game, a warrior and a mage character are created first, and then the attributes and behaviors of the characters are accessed through the base class pointer. When a warrior attacks a mage, it calls attackTarget()a function , and when a mage attacks a warrior, it calls attackTarget()a function in the mage class. Since these two functions are both virtual functions and have been redefined, they actually call the functions in the derived class, realizing runtime polymorphism.

! ! ! More examples of polymorphic programs! ! !

geometry handler

Geometry Handler: Input the parameters of several geometries,

Requires the output to be sorted by area. Specify the shape when exporting.

Input:

The first line is the number of geometric shapes n (not exceeding 100). There are n lines below, and each line starts with a letter c.

If c is 'R', it represents a rectangle, and this line is followed by two integers, which are the width and height of the rectangle;

If c is 'C', it represents a circle, followed by an integer representing its radius

If c is 'T', it represents a triangle, and this line is followed by three integers, representing the lengths of the three sides

Output:

Output the type and area of ​​each geometric shape in ascending order of area. One geometry per line, the output format is:

Shape name: area

Textbook code example

#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
class CShape
{
    
    
public:
virtual double Area() = 0; //纯虚函数
virtual void PrintInfo() = 0;
}; 
class CRectangle:public CShape
{
    
    
public:
int w,h; 
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape {
    
    
public:
int r; 
virtual double Area();
virtual void PrintInfo();
};
class CTriangle:public CShape {
    
    
public:
int a,b,c; 
virtual double Area();
virtual void PrintInfo();
}; 
double CRectangle::Area() {
    
     
return w * h; 
}
void CRectangle::PrintInfo() {
    
    
cout << "Rectangle:" << Area() << endl;
}
double CCircle::Area() {
    
    
return 3.14 * r * r ;
}
void CCircle::PrintInfo() {
    
    
cout << "Circle:" << Area() << endl;
}
double CTriangle::Area() {
    
    
double p = ( a + b + c) / 2.0;
return sqrt(p * ( p - a)*(p- b)*(p - c));
}
void CTriangle::PrintInfo() {
    
    
cout << "Triangle:" << Area() << endl; 
}
CShape * pShapes[100];
int MyCompare(const void * s1, const void * s2);
int main()
{
    
     
int i; int n;
CRectangle * pr; CCircle * pc; CTriangle * pt;
cin >> n;
for( i = 0;i < n;i ++ ) {
    
    
char c;
cin >> c;
switch(c) {
    
    
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr; 
break; 
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt; 
break;
} 
}
qsort(pShapes,n,sizeof( CShape*),MyCompare);
for( i = 0;i <n;i ++)
pShapes[i]->PrintInfo(); 
return 0;
}
int MyCompare(const void * s1, const void * s2)
{
    
    
double a1,a2;
CShape * * p1 ; // s1,s2 是 void * ,不可写 “* s1”来取得s1指向的内容
CShape * * p2;
p1 = ( CShape * * ) s1; //s1,s2指向pShapes数组中的元素,数组元素的类型是CShape *
p2 = ( CShape * * ) s2; // 故 p1,p2都是指向指针的指针,类型为 CShape ** 
a1 = (*p1)->Area(); // * p1 的类型是 Cshape * ,是基类指针,故此句为多态
a2 = (*p2)->Area();
if( a1 < a2 ) 
return -1;
else if ( a2 < a1 )
return 1;
else
return 0;
} 
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt; 
break;
} 
}
qsort(pShapes,n,sizeof( CShape*),MyCompare);
for( i = 0;i <n;i ++)
pShapes[i]->PrintInfo(); 
return 0;
}

If you add a new geometric shape, such as a pentagon, you only need to derive CPentagon from CShape, and add a case to the switch statement in the main, and the rest remains unchanged!

It is a very common practice to use the base class pointer array to store pointers to various derived class objects, and then traverse the array to perform various operations on each derived class object

下面是一个利用 C++ 实现的几何形体处理程序,可以输入若干个几何形体的参数,并按照面积排序输出各个几何形体的种类及面积:
```c++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const double pi = 3.14159265358979323846;
// 几何形体基类
class Shape {
    
    
public:
    virtual double getArea() = 0; // 获取面积
    virtual string getName() = 0; // 获取名称
};
// 具体几何形体类:矩形
class Rectangle : public Shape {
    
    
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) {
    
    
        width = w;
        height = h;
    }
    double getArea() {
    
     return width * height; }
    string getName() {
    
     return "矩形"; }
};
// 具体几何形体类:圆形
class Circle : public Shape {
    
    
private:
    double radius;
public:
    Circle(double r) {
    
     radius = r; }
    double getArea() {
    
     return pi * radius * radius; }
    string getName() {
    
     return "圆形"; }
};
// 具体几何形体类:三角形
class Triangle : public Shape {
    
    
private:
    double a, b, c;
public:
    Triangle(double aa, double bb, double cc) {
    
    
        a = aa;
        b = bb;
        c = cc;
    }
    double getArea() {
    
    
        double p = (a + b + c) / 2;
        return sqrt(p * (p - a) * (p - b) * (p - c));
    }
    string getName() {
    
     return "三角形"; }
};
// 比较函数,用于排序
bool cmp(Shape* s1, Shape* s2) {
    
     return s1->getArea() < s2->getArea(); }
// 主程序
int main() {
    
    
    int n;
    cin >> n;
    vector<Shape*> shapes;
    for (int i = 0; i < n; i++) {
    
    
        char c;
        cin >> c;
        if (c == 'R') {
    
    
            double w, h;
            cin >> w >> h;
            shapes.push_back(new Rectangle(w, h));
        }
        else if (c == 'C') {
    
    
            double r;
            cin >> r;
            shapes.push_back(new Circle(r));
        }
        else if (c == 'T') {
    
    
            double a, b, c;
            cin >> a >> b >> c;
            shapes.push_back(new Triangle(a, b, c));
        }
    }
    sort(shapes.begin(), shapes.end(), cmp);
    for (int i = 0; i < n; i++) {
    
    
        cout << shapes[i]->getName() << ":" << shapes[i]->getArea() << endl;
        delete shapes[i];
    }
    return 0;
}
```
在上述代码中,几何形体基类 `Shape` 定义了接口函数 `getArea()` 和 `getName()`,分别用于获取几何形体的面积和名称。具体的几何形体类 `Rectangle`、`Circle` 和 `Triangle` 继承自 `Shape`,并实现了这两个接口函数。

在主程序中,首先输入几何形体的数目 `n`,然后根据每个几何形体的名称和参数创建相应的对象,并添加到 `shapes` 向量中。最后利用 `sort()` 函数和比较函数 `cmp()` 对 `shapes` 向量进行排序,按照面积从小到大排序。最后遍历 `shapes` 向量,输出每个几何形体的名称和面积,并释放相应的内存。

Calling virtual functions 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 runtime to decide whether to call its own or a derived class function

Calling virtual functions in constructors and destructors is a dangerous practice, because the call of virtual functions is determined at runtime, and in constructors and destructors, the state of the object may not be fully initialized or has been Destroyed, at which point calling a virtual function may produce undefined behavior.

Specifically, calling virtual functions in constructors can cause the following problems:

  1. The object may not be fully initialized, and calling a virtual function at this time may access uninitialized member variables or invalid pointers, causing the program to crash or produce undefined behavior.
  2. The dynamic type of the object may not be determined yet. At this time, calling the virtual function will use the implementation of the base class by default instead of the implementation of the derived class, resulting in wrong behavior of the program.
    Calling a virtual function in a destructor can cause the following problems:
  3. The dynamic type of the object may have been destroyed, and calling the virtual function at this time will cause the program to crash or produce undefined behavior.
  4. Calling a virtual function may trigger the lookup and call of the virtual function table, and during the process of object destruction, the virtual function table and virtual function pointer may have been destroyed, resulting in undefined behavior of the program.

Therefore, you should try to avoid calling virtual functions in constructors and destructors, or adopt other solutions, such as using initialization lists in constructors to initialize member variables, or only perform simple resource release operations in destructors. If you really need to call a virtual function in a constructor or destructor, you should try to ensure that the state of the object has been fully initialized or has not been destroyed, and you should pay attention to whether the implementation of the virtual function can handle this situation correctly.

The key to polymorphic implementation - virtual function table

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-M8dnzr3H-1687264779370)(2023-06-20-20-22-42.png)] Polymorphism is
for One of the important features of object programming, the key is to realize the dynamic dispatch of function calls through runtime binding. In C++, a common way to achieve polymorphism is through virtual functions. The key is to use the virtual function table (vtable) to store the virtual function pointer of the class, so as to realize dynamic binding.

The virtual function table is a static variable automatically generated by the compiler to store the virtual function pointer of the class. For each class that contains virtual functions, the compiler will generate a virtual function table for it at compile time, and store it in the data segment of the program as metadata of the class. Each entry in the virtual function table is a pointer to virtual functions in the same order as they were declared. Therefore, when an object of a class is created, the address of the virtual function table will be stored in the head of the object and become the virtual function pointer (vptr) of the object.

When using a pointer or reference of the base class type to call a virtual function, the virtual function table will be searched according to the actual type of the object, and the corresponding virtual function will be called through the virtual function pointer. Specifically, the compiler will look up the corresponding virtual function in the virtual function table pointed to by the virtual function pointer (vptr) of the object, and then call the corresponding function through the function pointer. Therefore, since the call of the virtual function is dynamically determined at runtime, the purpose of polymorphism can be achieved.

It should be noted that when using virtual functions, the following conditions need to be met:

  1. A virtual function must be a member function of the class.
  2. A virtual function must be declared as a virtual function in the base class and be overridden or implemented in the derived class.
  3. The parameter and return types of the virtual function must be exactly the same as the virtual function in the base class.
  4. The size and order of the virtual function table must correspond to the inheritance relationship of the class.
  5. A virtual function call must be made through a pointer or reference of the base class type to trigger dynamic binding.

Through the realization of the virtual function table, C++ realizes the function of polymorphism, which makes the program more flexible and extensible. However, it should be noted that the implementation of the virtual function table also increases the overhead and complexity of the program, so it needs to be carefully considered when designing and implementing the program.
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-9zSU9uGz-1687264779370)(2023-06-20-20-24-16.png)]

virtual destructor

When deleting a derived class object through a pointer of the base class, usually only the destructor of the base class is called

However, when deleting an object of a derived class, the destructor of the derived class should be called first, and then the destructor of the base class should be called.

Solution: Declare the destructor of the base class as virtual The destructor of the derived class can be declared as virtual

When deleting a derived class object through a pointer of the base class, the destructor of the derived class is called first, and then the destructor of the base class is called

Generally speaking, if a class defines a virtual function, the destructor should also be defined as a virtual function. Alternatively, a class intended to be used as a base class should also define the destructor as a virtual function.

Note: Virtual functions are not allowed as constructors

A virtual destructor refers to adding the keyword "virtual" before the destructor to implement a polymorphic destructor.
When using a base class pointer or reference to delete an object of a derived class, if the destructor of the base class is not a virtual function, then only the destructor of the base class will be called, not the destructor of the derived class. This will cause the memory resources allocated in the derived class to not be released correctly, resulting in memory leaks.
By declaring the destructor of the base class as a virtual function, dynamic binding can be realized when the object is destructed, and the destructor of the derived class can be called. In this way, the memory resources allocated in the derived class can be released correctly, and the problem of memory leaks can be avoided.
For example:

class Base {
public:
    virtual ~Base() { // 声明为虚函数
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    ~Derived() override { // 实现派生类的析构函数
        std::cout << "Derived::~Derived()" << std::endl;
    }
};
int main() {
    Base *p = new Derived();
    delete p; // 动态绑定,调用派生类的析构函数
    return 0;
}

In the above code, the destructor of the base class is declared as a virtual function, and the destructor of the derived class is implemented and overrides the virtual destructor of the base class. When deleting the derived class object, delete it through the pointer of the base class, and use the dynamic binding mechanism to realize the calling of the derived class destructor.

The virtual destructor is to solve the memory leak problem that may occur when the base class pointer deletes the derived class object, and it is the key to realize polymorphism. When designing and implementing the object inheritance system, the destructor should be declared as a virtual function as much as possible.

It should be noted that if the derived class does not explicitly provide a destructor, the compiler will automatically generate a default destructor. This default destructor will also be declared virtual because it inherits the virtual destructor of the base class.
In addition, it should be noted that the virtual destructor is only applicable when the base class pointer or reference deletes the derived class object. If the direct name of the object is used for deletion, only the destructor of the object itself will be called, and dynamic binding will not be triggered, nor will the destructor of the derived class be called.
For example:

class Base {
public:
    virtual ~Base() { // 声明为虚函数
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    ~Derived() override { // 实现派生类的析构函数
        std::cout << "Derived::~Derived()" << std::endl;
    }
};
int main() {
    Derived d;
    Base *p = &d;
    p->~Base(); // 不会触发动态绑定,只会调用 Base 的析构函数
    return 0;
}

In the above code, the destructor of the object d is the destructor of the derived class Derived, which will be called automatically when the object d is deleted. However, if the base class pointer is used to directly call the object's destructor, dynamic binding will not be triggered, and only the base class's destructor will be called, resulting in the derived class's destructor not being called correctly.

Therefore, when using virtual destructors, you need to pay attention to using base class pointers or references to delete derived class objects to ensure that the destructors of derived classes are called correctly and avoid memory leaks.

In addition, it should be noted that if the destructor of the derived class needs to perform special resource release operations, such as releasing dynamically allocated memory or closing files, etc., then the destructor of the base class must be explicitly called in the destructor of the derived class. virtual destructor. Only in this way can it be ensured that when the derived class object is destructed, the destructor of the derived class is called first, and then the destructor of the base class is called, thereby avoiding the problem of resource leakage.
For example:

class Base {
public:
    virtual ~Base() {
        std::cout << "Base::~Base()" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() {
        data = new int[10];
    }
    ~Derived() override {
        delete[] data; // 释放动态分配的内存
        std::cout << "Derived::~Derived()" << std::endl;
        // 显式调用基类的虚析构函数
        // ensure the base class's destructor is executed
        Base::~Base();
    }
private:
    int *data;
};
int main() {
    Base *p = new Derived();
    delete p;
    return 0;
}

In the above code, the derived class Derived allocates the memory space of an int array in the constructor, and then releases the memory space in the destructor. At the same time, the virtual destructor of the base class is explicitly called in the destructor of the derived class to ensure that when the object is destructed, the destructor of the derived class is called first, and then the destructor of the base class is called, thereby avoiding The problem of resource leaks.

In short, the virtual destructor is the key to polymorphism and avoid memory leaks. You need to pay attention to using base class pointers or references to delete derived class objects, and to explicitly call the virtual destructor of the base class in the destructor of the derived class. function to ensure that resources are properly released.

Pure virtual functions and abstract classes

A class that contains pure virtual functions is called an abstract class

 Abstract classes can only be used as base classes to derive new classes, and cannot create independent abstract class objects

 The pointer and reference of the abstract class can point to the object of the class derived from the abstract class

A a ; // Wrong, A is an abstract class, objects cannot be created
A * pa ; // ok, pointers and references of abstract classes can be defined
pa = new A ; // Error, A is an abstract class, objects cannot be created

 Pure virtual functions can be called in member functions of abstract classes, but cannot be called in constructors or destructors.

 If a class is derived from an abstract class, it can become a non-abstract class if and only if it implements all the pure virtual functions in the base class.

Pure virtual functions and abstract classes are important features in C++ for implementing interfaces and polymorphism.
A pure virtual function is a function specified with "= 0" at the end of the function declaration, which is a virtual function without implementation. The role of pure virtual functions is to implement interfaces, that is, to define an interface without providing a specific implementation. Since the pure virtual function has no specific implementation, it cannot directly create an object of this class, and the function must be implemented in a derived class before it can be used. For example:

class Shape {
public:
    virtual double area() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
    double area() override { // 实现纯虚函数
        return 3.14 * radius * radius;
    }
private:
    double radius;
};

An abstract class refers to a class containing pure virtual functions, which cannot be instantiated directly, but can only be used as a base class for deriving other classes. The role of an abstract class is to define a set of interfaces without providing a specific implementation. Since abstract classes contain pure virtual functions, all pure virtual functions must be implemented in derived classes before they can be used. For example:

class Shape {
public:
    virtual double area() = 0; // 纯虚函数,使得 Shape 变成了抽象类
    virtual void draw() = 0;   // 纯虚函数
};
class Circle : public Shape {
public:
    double area() override {
        return 3.14 * radius * radius;
    }
    void draw() override {
        // 绘制圆形
    }
private:
    double radius;
};

It should be noted that abstract classes can contain non-pure virtual functions, but classes containing pure virtual functions must be abstract classes. Because a class containing pure virtual functions cannot be instantiated directly, it must be used as a base class for deriving other classes, providing the definition of an interface.

The role of pure virtual functions and abstract classes is to implement interfaces and polymorphism, making programs more flexible and extensible. In actual program design, abstract classes or pure virtual functions can be used to define interfaces, which makes program design more modular and maintainable.

Guess you like

Origin blog.csdn.net/shaozheng0503/article/details/131315126