什么是析构函数
析构函数是一种特殊的类成员函数,当该类对象被销毁时自动执行。构造函数负责初始化,而析构函数专门用于清理资源。
当对象正常离开作用域,或通过 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() 终止程序,不会调用任何析构函数。若依赖析构函数完成必要清理(如写日志、关闭数据库连接),请务必小心。
小结
当构造函数与析构函数协同工作时,类可在创建时自我初始化,在销毁时自我清理,无需程序员额外介入。这不仅降低出错概率,也使类更易使用。