C++中何时需要虚析构函数:2025年解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C++中的虚析构函数:为何至今仍至关重要(以及何时可以忽略)

\n\n

如果你曾经写过C++代码并接触过继承,你肯定在Stack Overflow上见过这个拥有数万浏览量的问题:“When to use virtual destructors?”。在2025年,这个话题依然热度不减——由于遗漏virtual导致的内存泄漏在生产环境中依然屡见不鲜。但另一方面,在任何地方都盲目添加virtual也并非最佳实践。

\n\n

让我们一劳永逸地搞清楚:虚析构函数何时是必需的,何时只是锦上添花,而何时它的存在反而意味着糟糕的架构。开始吧。

\n\n

简短回答(给赶时间的人)

\n\n

虚析构函数仅在一种情况下是必需的:当你打算通过基类指针删除派生类对象时。如果代码中没有这种场景,则不需要virtual。但下面我们会讨论一些细节。

\n\n

为什么这会是问题?内存泄漏的剖析

\n\n

让我们看一个典型的反模式,它常常让新手(甚至不止新手)的代码出问题。

\n\n
#include <iostream>\n\nclass Base {\npublic:\n    ~Base() { \n        std::cout << "Base destructor\\n"; \n    }\n};\n\nclass Derived : public Base {\nprivate:\n    int* big_data = new int[1000]; // 需要释放的资源\npublic:\n    Derived() { std::cout << "Derived created\\n"; }\n    ~Derived() {\n        delete[] big_data;\n        std::cout << "Derived destructor\\n";\n    }\n};\n\nint main() {\n    Base* ptr = new Derived();\n    delete ptr;  // 糟糕!只会调用~Base()\n    return 0;\n}
\n\n

输出:

\n
Derived created\nBase destructor
\n\n

数组big_data泄漏了。Derived的析构函数没有被调用,因为编译器看到类型是Base*,所以调用了该类的析构函数。而它并不是虚函数。

\n\n

解决方案——在Base的析构函数前添加virtual:

\n\n
class Base {\npublic:\n    virtual ~Base() = default;  // 或者空函数体\n};
\n\n

现在,当执行delete ptr时,会先调用~Derived(),然后调用~Base()。资源被释放,内存不再泄漏,静态分析器也满意了。

\n\n

何时虚析构函数是必需的(铁律)

\n\n

只有一种绝对必要的情况:如果一个类旨在通过基类指针或引用进行多态使用,并且派生类的对象将通过该指针被删除。

\n\n

在实践中,这意味着:

\n
    \n
  • 该类至少有一个虚函数(纯虚或非纯虚)
  • \n
  • 你计划使用Base*或Base&来操作Derived
  • \n
  • 对象在堆上创建(new)并通过delete删除
  • \n
\n\n

示例——经典的形状层次结构:

\n\n
class Shape {\npublic:\n    virtual double area() const = 0;\n    virtual ~Shape() = default;  // 抽象类必须使用\n};\n\nclass Circle : public Shape {\n    double radius;\npublic:\n    explicit Circle(double r) : radius(r) {}\n    double area() const override { return 3.14159 * radius * radius; }\n};\n\nint main() {\n    std::vector<std::unique_ptr<Shape>> shapes;\n    shapes.push_back(std::make_unique<Circle>(5.0));\n    // unique_ptr会自动处理delete,但Shape的析构函数必须是virtual\n    return 0;\n}
\n\n

请注意:即使你使用std::unique_ptr(这是2025年推荐的做法),虚析构函数仍然是必需的。unique_ptr通过基类指针调用delete——

博客

书籍推荐