C++ 析构函数详解

什么是析构函数

析构函数是一种特殊的类成员函数,当该类对象被销毁时自动执行。构造函数负责初始化,而析构函数专门用于清理资源。

当对象正常离开作用域,或通过 delete 显式销毁动态分配的对象时,若存在析构函数,系统会在对象从内存中移除之前自动调用它,完成必要的清理工作。对于仅含普通成员变量的简单类,C++ 会自动回收内存,无需自定义析构函数;但若类持有资源(如动态内存、文件句柄、数据库连接等),或需要在对象销毁前执行维护操作,则应将逻辑放在析构函数中,这是最后且最合适的时机。

命名规则

与构造函数类似,析构函数遵循严格的命名约定:

  • 必须与类同名,且前缀为波浪号 ~
  • 不得接受任何参数;
  • 无返回类型;
  • 每个类只能有一个析构函数。

一般而言,不应显式调用析构函数(因其会在对象销毁时自动触发),极少需要多次清理同一对象。然而,析构函数内部可安全调用其他成员函数,因为对象只有在析构函数执行完毕后才真正销毁。

析构函数示例

以下示例展示了一个简单类如何使用析构函数:

#include <iostream>
#include <cassert>
#include <cstddef>

class IntArray
{
private:
    int* m_array{};
    int m_length{};

public:
    IntArray(int length) // 构造函数
    {
        assert(length > 0);
        m_array = new int[static_cast<std::size_t>(length)]{};
        m_length = length;
    }

    ~IntArray() // 析构函数
    {
        // 释放先前分配的数组
        delete[] m_array;
    }

    void setValue(int index, int value) { m_array[index] = value; }
    int getValue(int index) { return m_array[index]; }
    int getLength() { return m_length; }
};

int main()
{
    IntArray ar(10); // 分配 10 个整数
    for (int count{ 0 }; count < ar.getLength(); ++count)
        ar.setValue(count, count + 1);

    std::cout << "第 5 个元素的值为: " << ar.getValue(5) << '\n';
    return 0;
} // ar 在此离开作用域,~IntArray() 被自动调用

提示
若编译上述示例时出现以下错误:

error: 'class IntArray' has pointer data members [-Werror=effc++]
error:   but does not override 'IntArray(const IntArray&)' [-Werror=effc++]
error:   or 'operator=(const IntArray&)' [-Werror=effc++]

可在编译设置中移除 -Weffc++ 标志,或在类内添加:

IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;
(`=delete` 用法参见《拷贝构造函数简介》)

程序运行结果:  

第 5 个元素的值为: 6

在 main() 第一行创建 IntArray 对象 ar 并传入长度 10,构造函数动态分配数组内存。main() 结束时 ar 离开作用域,析构函数自动释放数组。  

提醒  
在 《std::vector 与列表构造函数简介》中提到,当初始化数组/容器/列表类且仅指定长度而非元素列表时,应使用括号初始化。因此示例采用 `IntArray ar(10);`。

## 构造与析构的时机  
以下示例在构造与析构函数中加入打印语句,以展示调用顺序:

```cpp
#include <iostream>

class Simple
{
private:
    int m_nID{};

public:
    Simple(int nID) : m_nID{ nID }
    {
        std::cout << "构造 Simple " << nID << '\n';
    }

    ~Simple()
    {
        std::cout << "析构 Simple" << m_nID << '\n';
    }

    int getID() { return m_nID; }
};

int main()
{
    Simple simple{ 1 };         // 栈上分配
    std::cout << simple.getID() << '\n';

    Simple* pSimple{ new Simple{ 2 } }; // 动态分配
    std::cout << pSimple->getID() << '\n';

    delete pSimple;             // 手动销毁

    return 0;
} // simple 在此离开作用域

输出:

构造 Simple 1
1
构造 Simple 2
2
析构 Simple 2
析构 Simple 1

由于先 delete pSimple,后离开 main(),故 “Simple 1” 在 “Simple 2” 之后析构。

全局变量在 main() 之前构造,在 main() 之后析构。

RAII

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种编程技术,将资源使用与具有自动存储期的对象生命周期绑定。在 C++ 中,通过类的构造函数获取资源,在析构函数释放资源:

  • 通常在构造函数中申请资源(如内存、文件句柄等),
  • 在析构函数中释放资源。
    RAII 的最大优势是防止资源泄漏——所有持有资源的对象都会被自动清理。

本课顶部的 IntArray 类即 RAII 示例:构造函数分配,析构函数释放。std::string 与 std::vector 也是标准库中遵循 RAII 的类:初始化时获取动态内存,销毁时自动清理。

关于 std::exit() 的警告

若使用 std::exit() 终止程序,不会调用任何析构函数。若依赖析构函数完成必要清理(如写日志、关闭数据库连接),请务必小心。

小结

当构造函数与析构函数协同工作时,类可在创建时自我初始化,在销毁时自我清理,无需程序员额外介入。这不仅降低出错概率,也使类更易使用。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"