虚析构函数:解决子类对象的内存泄漏

虚析构函数:解决子类对象的内存泄漏

在前面两篇博客中,我们先后讲解了虚函数与动态多态、纯虚函数与抽象类,核心围绕"动态绑定"和"接口规范"展开------用父类指针/引用指向子类对象,实现"一个接口,多种实现",这也是C++面向对象编程的核心用法。但很多初学者在使用这一特性时,会忽略一个隐藏的"致命问题":子类对象的内存泄漏

当用父类指针指向子类对象,并且子类对象包含堆内存分配时,如果父类的析构函数不是虚函数,delete父类指针时,编译器只会调用父类的析构函数,子类的析构函数不会被执行------这就导致子类中分配的堆内存无法释放,长期运行会造成内存泄漏,甚至导致程序崩溃。而解决这一问题的关键,就是我们今天的主角:虚析构函数(Virtual Destructor)

很多初学者对虚析构函数存在认知误区:要么不知道它的存在,要么误以为"只要加了virtual就是虚析构函数,随便用",要么混淆"普通析构函数""虚析构函数""纯虚析构函数"的区别。本文将从"内存泄漏痛点"切入,先演示未使用虚析构函数时的内存泄漏问题,再详解虚析构函数的语法、工作原理,结合实战场景演示其用法,对比三种析构函数的差异,规避高频误区,同时衔接前序虚函数、抽象类知识点,帮你彻底打通"动态多态→虚析构函数→内存安全"的逻辑链,确保写出的多态代码既灵活,又无内存隐患。

核心前提回顾:1. 动态多态的实现条件(父类虚函数+子类重写+父类指针/引用指向子类对象);2. 抽象类的特性(包含纯虚函数,不能实例化,可定义指针/引用);3. 堆内存的分配与释放(new分配的内存必须用delete释放,否则会内存泄漏)。

一、先看痛点:未用虚析构函数的内存泄漏灾难

在讲解虚析构函数之前,我们先通过一个真实的开发场景,直观感受"未使用虚析构函数"导致的内存泄漏问题------这个问题在多态开发中极其常见,也是笔试、面试的高频考点。

场景再现:图形类的内存泄漏问题

延续前两篇博客的图形计算系统,我们定义抽象类Shape(图形),子类Circle(圆形)包含堆内存分配(存储半径),用父类指针指向子类对象,最后delete父类指针,观察内存释放情况:

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;

// 抽象类:Shape(图形),包含纯虚函数
class Shape {
public:
    // 纯虚函数:计算面积(接口规范)
    virtual double calculateArea() = 0;

    // 普通析构函数(未加virtual)
    ~Shape() {
        cout << "Shape析构函数调用:释放Shape类资源" << endl;
    }
};

// 子类:Circle(圆形),包含堆内存分配
class Circle : public Shape {
private:
    double* radius; // 堆内存指针,存储圆形半径

public:
    // 构造函数:分配堆内存
    Circle(double r) {
        radius = new double(r); // 动态分配堆内存,存储半径值
        cout << "Circle构造函数调用:分配堆内存(半径:" << *radius << ")" << endl;
    }

    // 重写纯虚函数:计算圆形面积
    virtual double calculateArea() {
        return M_PI * (*radius) * (*radius);
    }

    // 子类析构函数:释放堆内存
    ~Circle() {
        delete radius; // 释放堆内存,避免内存泄漏
        radius = nullptr; // 置空指针,防止野指针
        cout << "Circle析构函数调用:释放堆内存(radius指针)" << endl;
    }
};

int main() {
    // 父类指针(抽象类指针)指向子类对象(堆内存分配)
    Shape* shapePtr = new Circle(5.0);

    // 调用子类方法(动态绑定,正常执行)
    cout << "圆形面积:" << shapePtr->calculateArea() << endl;

    // 释放父类指针,期望释放子类对象的所有资源
    delete shapePtr;
    shapePtr = nullptr;

    return 0;
}

运行结果与问题分析

运行结果(重点关注析构函数的调用顺序):

Plain 复制代码
Circle构造函数调用:分配堆内存(半径:5)
圆形面积:78.5398
Shape析构函数调用:释放Shape类资源

致命问题:Circle类的析构函数没有被调用

我们分析一下整个流程的内存变化:

  1. 执行new Circle(5.0):调用Circle构造函数,在堆内存中分配两块空间------一块存储Circle对象本身,另一块存储radius指针指向的半径值(5.0);

  2. 父类指针shapePtr指向这块Circle对象空间,调用calculateArea()时,因动态绑定,正常执行Circle的实现;

  3. 执行delete shapePtr:编译器只识别到shapePtr是Shape类型的指针,因此只调用Shape类的析构函数,释放Shape类的资源;

  4. Circle类的析构函数未被调用,导致radius指针指向的堆内存(存储半径的空间)无法释放------这就是内存泄漏

补充说明:内存泄漏的危害的是"累积性"的。如果这个代码片段在循环中执行(比如多次创建和删除图形对象),会导致越来越多的堆内存无法释放,最终耗尽系统内存,程序崩溃。在长期运行的服务端程序、嵌入式程序中,这种问题更是致命的。

问题根源:析构函数的"静态绑定"

为什么子类的析构函数没有被调用?核心根源在于:普通析构函数(未加virtual)是静态绑定的

静态绑定(编译期绑定):编译器在编译阶段,就根据指针的"声明类型"(而非实际指向的对象类型),确定要调用的析构函数。在上述代码中,shapePtr的声明类型是Shape*,因此编译器在编译时,就确定delete shapePtr时,调用的是Shape类的析构函数,而非Circle类的------这就导致子类的堆内存无法释放。

而我们需要的是:delete父类指针时,根据指针实际指向的对象类型,动态调用对应的析构函数 (指向Circle对象,就调用Circle的析构函数;指向Rectangle对象,就调用Rectangle的析构函数)------这就是"动态绑定",而实现动态绑定的关键,就是将父类的析构函数声明为虚析构函数

二、核心解决方案:虚析构函数的语法与工作原理

虚析构函数,本质上是"被声明为virtual的析构函数",它的核心作用是"让析构函数支持动态绑定"------delete父类指针时,编译器会根据指针实际指向的对象类型,调用对应的子类析构函数,再调用父类析构函数,确保子类和父类的资源都能被正确释放,彻底解决内存泄漏问题。

1. 虚析构函数的语法格式(极简)

虚析构函数的语法非常简单,只需在父类的析构函数声明前加上virtual关键字即可,子类的析构函数无需加virtual(编译器会自动识别为虚析构函数),但推荐加上,增强代码可读性。

cpp 复制代码
// 父类:声明虚析构函数(核心:virtual + ~类名())
class 父类名 {
public:
    // 虚析构函数(可提供实现,释放父类自身资源)
    virtual ~父类名() {
        // 父类资源的释放逻辑
    }

    // 其他虚函数/纯虚函数(可选)
    virtual void func() = 0;
};

// 子类:析构函数自动成为虚析构函数(推荐加virtual)
class 子类名 : public 父类名 {
private:
    // 子类堆内存成员
    数据类型* 指针成员;
public:
    子类名() {
        指针成员 = new 数据类型; // 分配堆内存
    }

    // 子类析构函数(释放子类堆内存)
    virtual ~子类名() {
        delete 指针成员; // 释放子类堆内存
        指针成员 = nullptr; // 置空指针
    }

    // 重写父类虚函数(可选)
    virtual void func() {
        // 子类实现
    }
};

关键提醒:虚析构函数的核心是"父类声明为virtual",子类的析构函数无论是否加virtual,都会被视为虚析构函数------因为虚函数具有"传递性"(父类声明虚函数,子类重写后,子类的子类也默认是虚函数)。

2. 用虚析构函数修复内存泄漏问题

我们修改上述图形类的代码,将Shape类的析构函数声明为虚析构函数,观察运行结果:

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;

// 抽象类:Shape,析构函数声明为虚析构函数(核心修改)
class Shape {
public:
    virtual double calculateArea() = 0;

    // 虚析构函数(加virtual)
    virtual ~Shape() {
        cout << "Shape析构函数调用:释放Shape类资源" << endl;
    }
};

class Circle : public Shape {
private:
    double* radius;
public:
    Circle(double r) {
        radius = new double(r);
        cout << "Circle构造函数调用:分配堆内存(半径:" << *radius << ")" << endl;
    }

    virtual double calculateArea() {
        return M_PI * (*radius) * (*radius);
    }

    // 子类析构函数(推荐加virtual)
    virtual ~Circle() {
        delete radius;
        radius = nullptr;
        cout << "Circle析构函数调用:释放堆内存(radius指针)" << endl;
    }
};

int main() {
    Shape* shapePtr = new Circle(5.0);
    cout << "圆形面积:" << shapePtr->calculateArea() << endl;

    delete shapePtr; // 动态绑定,先调用子类析构,再调用父类析构
    shapePtr = nullptr;

    return 0;
}

运行结果与原理分析

运行结果(析构函数调用顺序正确):

Plain 复制代码
Circle构造函数调用:分配堆内存(半径:5)
圆形面积:78.5398
Circle析构函数调用:释放堆内存(radius指针)
Shape析构函数调用:释放Shape类资源

核心原理(动态绑定的作用):

  1. 父类Shape的析构函数被声明为virtual后,析构函数就支持"动态绑定"------编译器在编译阶段,不再确定要调用的析构函数,而是等到运行时,根据指针实际指向的对象类型,动态绑定对应的析构函数;

  2. 执行delete shapePtr时,shapePtr实际指向的是Circle对象,因此先调用Circle类的析构函数,释放子类的堆内存(radius指针指向的空间);

  3. 子类析构函数执行完毕后,自动调用父类Shape的析构函数,释放父类的资源------整个析构流程完整,无内存泄漏。

补充说明:析构函数的调用顺序是"先子类,后父类",这与构造函数的调用顺序(先父类,后子类)正好相反,符合"先分配的资源后释放"的原则(子类资源依赖父类资源,因此父类先构造,子类后构造;子类先释放,父类后释放)。

3. 虚析构函数的底层原理(简单理解)

结合前一篇博客讲解的"虚函数表(vtable)+虚指针(vptr)",虚析构函数的底层原理其实很简单:

  1. 当父类声明虚析构函数后,编译器会将虚析构函数的地址存入父类的虚函数表中;

  2. 子类继承父类后,会继承父类的虚函数表,同时将自身析构函数的地址,替换虚函数表中"父类虚析构函数"的地址;

  3. 当用父类指针指向子类对象时,指针会继承子类对象的虚指针(vptr),指向子类的虚函数表;

  4. delete父类指针时,会通过虚指针查询虚函数表,找到子类析构函数的地址,先调用子类析构函数,再调用父类析构函数。

关键提醒:虚析构函数的底层实现,和普通虚函数完全一致,都是依赖虚函数表和虚指针的动态绑定------这也是为什么"父类声明virtual,子类就能自动支持动态析构"的原因。

三、延伸知识点:纯虚析构函数(抽象类的特殊处理)

在前一篇博客中,我们知道"包含纯虚函数的类是抽象类,不能实例化"。那抽象类的析构函数,能不能声明为纯虚析构函数呢?答案是:可以

纯虚析构函数,就是"被声明为纯虚函数的析构函数",它的核心作用是"既定义抽象类(强制子类重写),又确保子类对象的资源能被正确释放"。但纯虚析构函数有一个特殊要求:必须提供函数体(这是纯虚析构函数与普通纯虚函数的唯一区别)。

1. 纯虚析构函数的语法格式

cpp 复制代码
// 抽象类:包含纯虚析构函数(同时可包含其他纯虚函数)
class 抽象类名 {
public:
    // 纯虚析构函数:virtual + ~类名() = 0; 且必须提供函数体
    virtual ~抽象类名() = 0;

    // 其他纯虚函数(可选)
    virtual void func() = 0;
};

// 纯虚析构函数的函数体实现(必须写在类外部)
抽象类名::~抽象类名() {
    // 父类资源的释放逻辑(可选)
    cout << "抽象类纯虚析构函数调用" << endl;
}

// 子类:必须重写所有纯虚函数(除了纯虚析构函数,无需重写,只需实现自身析构)
class 子类名 : public 抽象类名 {
private:
    数据类型* 指针成员;
public:
    子类名() {
        指针成员 = new 数据类型;
    }

    // 重写其他纯虚函数
    virtual void func() {
        // 子类实现
    }

    // 子类析构函数(释放自身堆内存)
    virtual ~子类名() {
        delete 指针成员;
        cout << "子类析构函数调用" << endl;
    }
};

2. 纯虚析构函数的核心注意事项(必记)

  1. 纯虚析构函数必须提供函数体:普通纯虚函数(如func() = 0)可以不提供函数体,但纯虚析构函数必须提供------因为子类析构函数执行完毕后,会自动调用父类的析构函数,如果父类纯虚析构函数没有函数体,会导致链接错误。

  2. 子类无需重写纯虚析构函数:子类只需实现自身的析构函数即可,父类的纯虚析构函数的函数体,会在子类析构函数执行完毕后自动调用。

  3. 包含纯虚析构函数的类,依然是抽象类:只要类中包含至少一个纯虚函数(包括纯虚析构函数),该类就是抽象类,不能实例化,只能作为父类被子类继承。

3. 纯虚析构函数的实战演示

我们将图形类的Shape改为"包含纯虚析构函数的抽象类",演示其用法:

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;

// 抽象类:Shape,包含纯虚析构函数
class Shape {
public:
    // 纯虚函数:计算面积
    virtual double calculateArea() = 0;

    // 纯虚析构函数(必须提供函数体)
    virtual ~Shape() = 0;
};

// 纯虚析构函数的函数体实现(类外部)
Shape::~Shape() {
    cout << "Shape纯虚析构函数调用:释放Shape类资源" << endl;
}

// 子类:Circle
class Circle : public Shape {
private:
    double* radius;
public:
    Circle(double r) {
        radius = new double(r);
        cout << "Circle构造函数调用:分配堆内存(半径:" << *radius << ")" << endl;
    }

    // 重写纯虚函数
    virtual double calculateArea() {
        return M_PI * (*radius) * (*radius);
    }

    // 子类析构函数(无需重写纯虚析构,只需实现自身)
    virtual ~Circle() {
        delete radius;
        radius = nullptr;
        cout << "Circle析构函数调用:释放堆内存(radius指针)" << endl;
    }
};

// 子类:Rectangle
class Rectangle : public Shape {
private:
    double* length;
    double* width;
public:
    Rectangle(double l, double w) {
        length = new double(l);
        width = new double(w);
        cout << "Rectangle构造函数调用:分配堆内存(长:" << *length << ",宽:" << *width << ")" << endl;
    }

    virtual double calculateArea() {
        return (*length) * (*width);
    }

    virtual ~Rectangle() {
        delete length;
        delete width;
        length = nullptr;
        width = nullptr;
        cout << "Rectangle析构函数调用:释放堆内存(length和width指针)" << endl;
    }
};

int main() {
    Shape* ptr1 = new Circle(5.0);
    Shape* ptr2 = new Rectangle(4.0, 6.0);

    cout << "圆形面积:" << ptr1->calculateArea() << endl;
    cout << "矩形面积:" << ptr2->calculateArea() << endl;

    // 释放指针,测试纯虚析构函数
    delete ptr1;
    delete ptr2;
    ptr1 = nullptr;
    ptr2 = nullptr;

    return 0;
}

运行结果分析

Plain 复制代码
Circle构造函数调用:分配堆内存(半径:5)
Rectangle构造函数调用:分配堆内存(长:4,宽:6)
圆形面积:78.5398
矩形面积:24
Circle析构函数调用:释放堆内存(radius指针)
Shape纯虚析构函数调用:释放Shape类资源
Rectangle析构函数调用:释放堆内存(length和width指针)
Shape纯虚析构函数调用:释放Shape类资源

解析:

  1. Shape类包含纯虚析构函数,因此是抽象类,不能实例化,只能定义指针指向子类对象;

  2. delete父类指针时,先调用子类的析构函数(释放子类堆内存),再调用Shape类的纯虚析构函数(释放父类资源),无内存泄漏;

  3. 纯虚析构函数的函数体必须写在类外部,否则会导致链接错误------这是纯虚析构函数的特殊要求,一定要牢记。

四、实战场景:虚析构函数的高频使用场景

虚析构函数不是"万能的",也不是"所有类都需要加"------它的使用场景是明确的,只有当"用父类指针/引用指向子类对象,并且子类包含堆内存分配"时,才需要将父类的析构函数声明为虚析构函数。下面结合两个高频实战场景,巩固虚析构函数的用法。

场景1:多态场景下的子类堆内存管理(最常见)

需求:开发一个动物类系统,父类Animal(抽象类),子类Dog、Cat包含堆内存分配(存储名字),用父类指针管理子类对象,确保delete指针时无内存泄漏。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 抽象类:Animal
class Animal {
public:
    virtual void speak() = 0;

    // 虚析构函数(核心:解决子类内存泄漏)
    virtual ~Animal() {
        cout << "Animal析构函数调用" << endl;
    }
};

// 子类:Dog
class Dog : public Animal {
private:
    string* name; // 堆内存存储名字
public:
    Dog(const string& n) {
        name = new string(n);
        cout << "Dog构造函数:创建狗(名字:" << *name << ")" << endl;
    }

    virtual void speak() {
        cout << "狗" << *name << ":汪汪汪!" << endl;
    }

    virtual ~Dog() {
        delete name;
        name = nullptr;
        cout << "Dog析构函数:释放狗的名字内存" << endl;
    }
};

// 子类:Cat
class Cat : public Animal {
private:
    string* name;
public:
    Cat(const string& n) {
        name = new string(n);
        cout << "Cat构造函数:创建猫(名字:" << *name << ")" << endl;
    }

    virtual void speak() {
        cout << "猫" << *name << ":喵喵喵!" << endl;
    }

    virtual ~Cat() {
        delete name;
        name = nullptr;
        cout << "Cat析构函数:释放猫的名字内存" << endl;
    }
};

// 通用函数:用父类指针管理子类对象
void animalSpeak(Animal* animal) {
    animal->speak();
}

int main() {
    Animal* dogPtr = new Dog("旺财");
    Animal* catPtr = new Cat("橘橘");

    animalSpeak(dogPtr);
    animalSpeak(catPtr);

    // 释放指针,无内存泄漏
    delete dogPtr;
    delete catPtr;
    dogPtr = nullptr;
    catPtr = nullptr;

    return 0;
}

场景2:抽象类作为接口,子类实现具体功能

需求:开发一个插件框架,抽象类Plugin作为插件接口,子类LogPlugin(日志插件)包含堆内存分配(存储日志文件路径),框架用父类指针加载和销毁插件,确保插件资源完全释放。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 抽象类:Plugin(插件接口)
class Plugin {
public:
    virtual bool init(const string& config) = 0;
    virtual void run() = 0;

    // 纯虚析构函数(抽象类+解决内存泄漏)
    virtual ~Plugin() = 0;
};

// 纯虚析构函数实现
Plugin::~Plugin() {
    cout << "Plugin纯虚析构函数:释放插件接口资源" << endl;
}

// 子类:LogPlugin(日志插件)
class LogPlugin : public Plugin {
private:
    string* logPath; // 堆内存存储日志路径
public:
    virtual bool init(const string& config) {
        logPath = new string(config);
        cout << "日志插件初始化:日志路径=" << *logPath << endl;
        return true;
    }

    virtual void run() {
        cout << "日志插件运行:向" << *logPath << "写入日志" << endl;
    }

    virtual ~LogPlugin() {
        delete logPath;
        logPath = nullptr;
        cout << "日志插件析构:释放日志路径内存" << endl;
    }
};

// 插件框架:加载和销毁插件
class PluginFramework {
public:
    static Plugin* loadPlugin() {
        Plugin* plugin = new LogPlugin();
        plugin->init("/var/log/system.log");
        return plugin;
    }

    static void destroyPlugin(Plugin* plugin) {
        delete plugin; // 调用子类析构+父类纯虚析构
        plugin = nullptr;
    }
};

int main() {
    // 加载插件
    Plugin* plugin = PluginFramework::loadPlugin();
    // 运行插件
    plugin->run();
    // 销毁插件,无内存泄漏
    PluginFramework::destroyPlugin(plugin);

    return 0;
}

五、高频误区:虚析构函数的常见坑(笔试重点)

结合初学者的常见错误,聚焦"虚析构函数的使用场景、语法规则、与纯虚析构函数的区别",总结6个高频坑,每个坑对应错误示例和正确写法,帮你少走弯路,应对笔试考点。

误区1:所有类都加虚析构函数(过度使用)

cpp 复制代码
// 错误:普通类(不用于多态,子类无堆内存)加虚析构函数,浪费内存
class Person {
private:
    string name; // 无堆内存
public:
    virtual ~Person() {} // 多余:无需多态,无内存泄漏风险
};

// 正确:只有当"父类指针指向子类对象+子类有堆内存"时,才加虚析构函数
class Person {
private:
    string name;
public:
    ~Person() {} // 普通析构函数即可
};

关键提醒:虚析构函数会增加内存开销(对象会多一个虚指针,类会多一个虚函数表)------如果类不用于多态,或者子类没有堆内存分配,就不需要加虚析构函数,避免过度使用。

误区2:子类加虚析构函数,父类不加(本末倒置)

cpp 复制代码
class Shape {
public:
    virtual double calculateArea() = 0;
    ~Shape() {} // 错误:父类不加virtual,子类加了也没用
};

class Circle : public Shape {
private:
    double* radius;
public:
    Circle(double r) { radius = new double(r); }
    virtual ~Circle() { delete radius; } // 子类加virtual,无效
};

int main() {
    Shape* ptr = new Circle(5.0);
    delete ptr; // 依然只调用Shape析构,内存泄漏
    return 0;
}

关键提醒:虚析构函数的核心是"父类声明为virtual"------子类加virtual与否,不影响动态绑定的效果,只要父类不加virtual,就无法实现动态析构,依然会内存泄漏。

误区3:纯虚析构函数不提供函数体(链接错误)

cpp 复制代码
class Shape {
public:
    virtual double calculateArea() = 0;
    virtual ~Shape() = 0; // 错误:纯虚析构函数未提供函数体
};

// 正确:必须在类外部提供纯虚析构函数的函数体
Shape::~Shape() {
    // 可选的释放逻辑
}

关键提醒:普通纯虚函数可以不提供函数体,但纯虚析构函数必须提供------因为子类析构后会自动调用父类析构,没有函数体会导致链接错误。

误区4:子类没有堆内存,还加虚析构函数(多余)

cpp 复制代码
class Shape {
public:
    virtual double calculateArea() = 0;
    virtual ~Shape() {} // 多余:子类无堆内存,无需虚析构
};

class Circle : public Shape {
private:
    double radius; // 无堆内存(栈内存)
public:
    Circle(double r) : radius(r) {}
    virtual double calculateArea() { return M_PI * radius * radius; }
    // 子类析构函数无需写,编译器自动生成即可
};

int main() {
    Shape* ptr = new Circle(5.0);
    delete ptr; // 即使父类不加虚析构,也不会内存泄漏(子类无堆内存)
    return 0;
}

关键提醒:虚析构函数的作用是"释放子类的堆内存"------如果子类没有堆内存分配,即使delete父类指针时只调用父类析构,也不会有内存泄漏,此时加虚析构函数是多余的。

误区5:用父类引用指向子类对象,无需虚析构函数(错误)

cpp 复制代码
class Shape {
public:
    virtual double calculateArea() = 0;
    ~Shape() {} // 错误:即使是引用,也需要虚析构
};

class Circle : public Shape {
private:
    double* radius;
public:
    Circle(double r) { radius = new double(r); }
    virtual double calculateArea() { return M_PI * (*radius) * (*radius); }
    virtual ~Circle() { delete radius; }
};

int main() {
    // 父类引用指向子类对象(堆内存)
    Shape& shapeRef = *new Circle(5.0);
    delete &shapeRef; // 依然只调用Shape析构,内存泄漏
    return 0;
}

关键提醒:无论是"父类指针"还是"父类引用",只要指向子类对象(堆内存),并且子类有堆内存分配,就需要将父类的析构函数声明为虚析构函数------引用本质上也是指针的封装,静态绑定的问题依然存在。

误区6:混淆"虚析构函数"与"普通虚函数"的重写

cpp 复制代码
class Shape {
public:
    virtual ~Shape() { cout << "Shape析构" << endl; }
};

class Circle : public Shape {
public:
    // 错误:子类析构函数名写错(不是~Circle),无法实现动态析构
    virtual ~Circle1() { cout << "Circle析构" << endl; }
};

int main() {
    Shape* ptr = new Circle(5.0);
    delete ptr; // 只调用Shape析构,内存泄漏
    return 0;
}

关键提醒:子类析构函数的名字必须是"子类名",与父类析构函数名(父类名)不同,但编译器会自动识别为"重写父类虚析构函数"------如果子类析构函数名写错,就无法实现动态析构。

六、总结:虚析构函数的核心要点

虚析构函数是C++多态开发中"内存安全"的关键,其核心价值是"解决子类对象的内存泄漏",本质是"让析构函数支持动态绑定"。掌握它的使用场景、语法规则和常见误区,能帮你写出更健壮、更安全的面向对象代码,同时应对笔试、面试中的高频考点。

相关推荐
李元_霸1 小时前
前端监控实践
前端·性能优化
2501_901147831 小时前
幂函数实现的优化与工程思考笔记
笔记·算法·面试·职场和发展·php
好大的月亮1 小时前
中值法排序及LexoRank排序算法简述
java·算法·排序算法
前端程序猿i1 小时前
第 7 篇:性能优化 —— 大量消息下的流畅体验
前端·vue.js·性能优化
TongSearch1 小时前
Tongsearch分片的分配、迁移与生命周期管理
java·服务器·数据库·elasticsearch·tongsearch
t198751281 小时前
MATLAB水声信道建模:方法、实现与应用
开发语言·matlab
闻缺陷则喜何志丹1 小时前
【拆位法】P9277 [AGM 2023 资格赛] 反转|普及+
c++·算法·位运算·拆位法
maplewen.1 小时前
C++ 多态原理深入理解
开发语言·c++·面试
龙山云仓1 小时前
No152:AI中国故事-对话祖冲之——圆周率与AI精度:数学直觉与极限探索
大数据·开发语言·人工智能·python·机器学习