乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

虚析构函数

定义与作用

虚析构函数是基类的虚析构函数,确保通过基类指针 delete 派生类对象时,派生类的析构函数被正确调用:

class Base {
public:
    virtual ~Base() = default;  // 虚析构:正确释放派生类资源
};

如果基类析构函数不是虚的,delete 基类指针时只调用基类析构函数,派生类特化的资源永远不会释放——这是 C++ 中最经典的资源泄漏陷阱。

核心原理

非虚析构 vs 虚析构

构造与析构的调用链

完整示例

示例一:资源泄漏的经典案例

场景说明:数据库连接池中,连接基类析构非虚导致派生类资源泄漏——逐层修复。

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// ==== 错误版本:基类析构非虚 ====
class BadConnection {
public:
    BadConnection(std::string host) : host_(std::move(host)) {
        std::cout << "[连接] 连接 " << host_ << std::endl;
    }

    ~BadConnection() {  // 非虚析构!
        std::cout << "[断开] " << host_ << std::endl;
    }

    virtual void query(const std::string& sql) {
        std::cout << "普通查询: " << sql << std::endl;
    }

private:
    std::string host_;
};

class BadMySQLConnection : public BadConnection {
public:
    BadMySQLConnection(std::string host, std::string db)
        : BadConnection(std::move(host)), db_name_(std::move(db)),
          buffer_(new char[1024 * 1024]) {  // 分配 1MB 缓冲区
        std::cout << "  [MySQL] 选择数据库 " << db_name_
                  << ",分配缓冲区" << std::endl;
    }

    ~BadMySQLConnection() {
        std::cout << "  [MySQL] 释放缓冲区" << std::endl;
        delete[] buffer_;
    }

    void query(const std::string& sql) override {
        std::cout << "MySQL(" << db_name_ << ") 查询: " << sql << std::endl;
    }

private:
    std::string db_name_;
    char* buffer_;  // 动配资源
};

// ==== 正确版本:基类析构为虚 ====
class GoodConnection {
public:
    GoodConnection(std::string host) : host_(std::move(host)) {
        std::cout << "[安全连接] " << host_ << std::endl;
    }

    virtual ~GoodConnection() = default;  // 虚析构!

    virtual void query(const std::string& sql) {
        std::cout << "查询: " << sql << std::endl;
    }

private:
    std::string host_;
};

class GoodMySQLConnection : public GoodConnection {
public:
    GoodMySQLConnection(std::string host, std::string db)
        : GoodConnection(std::move(host)), db_name_(std::move(db)),
          buffer_(new char[1024 * 1024]) {
        std::cout << "  [MySQL] 选择数据库 " << db_name_
                  << ",分配缓冲区" << std::endl;
    }

    ~GoodMySQLConnection() override {
        std::cout << "  [MySQL] 正确释放缓冲区" << std::endl;
        delete[] buffer_;
    }

    void query(const std::string& sql) override {
        std::cout << "MySQL(" << db_name_ << ") 安全查询: " << sql << std::endl;
    }

private:
    std::string db_name_;
    char* buffer_;
};

int main() {
    std::cout << "===== BAD: 基类析构非虚 → 资源泄漏 =====" << std::endl;
    {
        BadConnection* conn = new BadMySQLConnection("localhost", "school_db");
        conn->query("SELECT * FROM students");
        delete conn;  // 只调用 BadConnection::~BadConnection !!!
        // BadMySQLConnection::~BadMySQLConnection 不会调用
        // 1MB 缓冲区泄漏!
        std::cout << "(conn 已删除,但缓冲区泄漏了)" << std::endl;
    }

    std::cout << "\n===== GOOD: 基类析构为虚 → 正确释放 =====" << std::endl;
    {
        GoodConnection* conn = new GoodMySQLConnection("localhost", "school_db");
        conn->query("SELECT * FROM students");
        delete conn;  // 正确:GoodMySQLConnection::~ → GoodConnection::~
    }

    // 更安全的做法:智能指针 + 虚析构
    std::cout << "\n===== BEST: 智能指针 + 虚析构 =====" << std::endl;
    {
        std::unique_ptr<GoodConnection> conn =
            std::make_unique<GoodMySQLConnection>("localhost", "school_db");
        conn->query("SELECT * FROM students");
    }  // unique_ptr 自动释放,正确调用派生类析构

    return 0;
}

预期输出:

===== BAD: 基类析构非虚 → 资源泄漏 =====
[连接] 连接 localhost
  [MySQL] 选择数据库 school_db,分配缓冲区
MySQL(school_db) 查询: SELECT * FROM students
[断开] localhost
(conn 已删除,但缓冲区泄漏了)

===== GOOD: 基类析构为虚 → 正确释放 =====
[安全连接] localhost
  [MySQL] 选择数据库 school_db,分配缓冲区
MySQL(school_db) 安全查询: SELECT * FROM students
  [MySQL] 正确释放缓冲区

===== BEST: 智能指针 + 虚析构 =====
[安全连接] localhost
  [MySQL] 选择数据库 school_db,分配缓冲区
MySQL(school_db) 安全查询: SELECT * FROM students
  [MySQL] 正确释放缓冲区

逐段分析:

  • BAD 版本:delete conn 只调用了 BadConnection::~BadConnection(),BadMySQLConnection 的 1MB 缓冲区永远不会释放——经典资源泄漏
  • GOOD 版本:基类析构是虚函数,delete conn 正确先调 ~GoodMySQLConnection() 再调 ~GoodConnection()
  • BEST 版本:unique_ptr<GoodConnection> + 虚析构——智能指针自动管理生命周期,结合虚析构保证正确释放
  • 关键原则:如果类有任何虚函数(说明将被多态使用),析构函数必须是虚的

示例二:资源池的实际应用

#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <chrono>

// 资源基类:虚析构保证正确清理
class Resource {
public:
    Resource(std::string id) : id_(std::move(id)) {}
    virtual ~Resource() = default;
    virtual void close() = 0;
    virtual std::string type() const = 0;
    const std::string& id() const { return id_; }

private:
    std::string id_;
};

class FileHandle : public Resource {
public:
    FileHandle(std::string id, std::string path)
        : Resource(std::move(id)), path_(std::move(path)) {
        std::cout << "  打开文件: " << path_ << std::endl;
    }
    ~FileHandle() override { close(); }

    void close() override {
        std::cout << "  关闭文件: " << path_ << std::endl;
    }

    std::string type() const override { return "文件"; }

private:
    std::string path_;
};

class SocketHandle : public Resource {
public:
    SocketHandle(std::string id, int port)
        : Resource(std::move(id)), port_(port) {
        std::cout << "  打开端口: " << port_ << std::endl;
    }
    ~SocketHandle() override { close(); }

    void close() override {
        std::cout << "  关闭端口: " << port_ << std::endl;
    }

    std::string type() const override { return "网络"; }

private:
    int port_;
};

int main() {
    std::cout << "===== 资源池(依赖虚析构正确清理) =====" << std::endl;

    std::vector<std::unique_ptr<Resource>> pool;
    pool.push_back(std::make_unique<FileHandle>("f1", "data.txt"));
    pool.push_back(std::make_unique<SocketHandle>("s1", 8080));
    pool.push_back(std::make_unique<FileHandle>("f2", "config.json"));

    std::cout << "\n资源池包含:" << std::endl;
    for (const auto& r : pool) {
        std::cout << "  " << r->type() << ": " << r->id() << std::endl;
    }

    std::cout << "\n清理资源池..." << std::endl;
    // unique_ptr 自动调用 delete,虚析构保证每个子类的 close() 被调用
    pool.clear();

    return 0;
}

预期输出:

===== 资源池(依赖虚析构正确清理) =====
  打开文件: data.txt
  打开端口: 8080
  打开文件: config.json

资源池包含:
  文件: f1
  网络: s1
  文件: f2

清理资源池...
  关闭文件: config.json
  关闭端口: 8080
  关闭文件: data.txt

易错场景与面试考点

易错场景

场景错误表现正确做法
基类有虚函数但析构非虚派生类资源泄漏所有多态基类的析构函数必须为 virtual
std::unique_ptr<Base> 使用非虚析构的 BaseUndefined Behavior基类析构必须 virtual
析构函数调用虚函数只调基类版本析构中不调虚函数
最终类(final)析构为虚性能损耗可忽略最终类析构不必是虚的
误以为不需要虚析构(如 CRTP)CRTP 基类析构通常也应是 virtual除非确定不会通过基类指针 delete

常见面试问题

  1. 为什么基类的析构函数需要是虚函数?——当通过基类指针 delete 派生类对象时,如果析构非虚,只调用基类析构,派生类资源泄漏。虚析构确保正确的析构调用链。

  2. 什么时候析构函数可以不设为 virtual?——当类不会被继承(final)、不会通过基类指针 delete、或者继承是 private 且仅作实现复用时。

  3. 纯虚析构函数和普通虚析构的区别?——纯虚析构 = 0 使类成为抽象类,但仍需提供函数体。普通虚析构不强制类为抽象类。

  4. 析构函数的调用顺序?——派生类析构 → 成员析构(声明逆序) → 基类析构。这是虚析构底层的保证。

  5. std::shared_ptr 对虚析构有什么特殊处理?——shared_ptr 在构造时捕获删除器,即使基类析构非虚,shared_ptr<Base>(new Derived()) 也能正确调用派生类析构。但不应依赖此特性——基类析构仍应该是虚的。

小结

  • 任何可能作为多态基类的类,析构函数必须为 virtual
  • 判断准则:如果有任何虚函数 → 析构一定是虚的
  • 非虚析构 + delete 基类指针 = 派生类资源泄漏(C++ 经典陷阱 Top 3)
  • unique_ptr 没有 shared_ptr 的类型擦除机制——基类析构非虚时更危险
  • 纯虚析构使类抽象化,但必须提供函数体
上一页
虚函数表与动态绑定原理
下一页
抽象类与纯虚函数