C++ 析构函数
析构函数是 C++ 中的一个特殊成员函数,它在对象生命周期结束时自动被调用。与构造函数负责初始化对象不同,析构函数负责在对象被销毁前进行必要的清理操作。
析构函数的名称由 ~
开头,后面跟着类名,它不接受任何参数,也不返回任何值。每个类只能有一个析构函数。
析构函数主要做什么?
析构函数在 C++ 的内存管理和资源控制中起着关键的作用,比如,释放对象动态分配的内存:当一个对象在堆上分配了内存(使用 new 关键字),这些内存需要在对象被销毁时释放,否则会造成内存泄漏。除了动态分配的内存,析构函数也可以确保对象可能持有的其他资源被正确释放,比如,文件句柄、数据库连接、网络资源、互斥锁等。C++ 遵循一种 RAII(Resource Acquisition Is Initialization)的设计模式,它保证了资源的获取在对象初始化时进行,而资源的释放在对象销毁时进行。析构函数是RAII模式的关键组成部分。
如何定义和使用析构函数
class ClassName {
public:
// 构造函数
ClassName() {
// 初始化代码
}
// 析构函数
~ClassName() {
// 清理代码
}
};
析构函数在以下几种情况下会被自动调用:
- 当局部对象(栈上的对象)离开作用域时。
- 当使用 delete 或 delete[] 删除东涛分配的对象时。
- 当一个包含对象成员的外部对象被销毁时,其对象成员的析构函数会被自动调用。
- 当程序结束时,全局或静态对象会被销毁。
虚析构函数
当涉及到继承和多态时,析构函数常常需要被声明为虚函数。
class Base {
public:
Base() {}
virtual ~Base() {
std::cout << "Base析构函数" << std::endl;
}
};
class Derived : public Base {
private:
int* resource;
public:
Derived() {
resource = new int[10];
}
~Derived() override {
std::cout << "Derived析构函数" << std::endl;
delete[] resource;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 如果~Base()不是虚函数,则只会调用Base的析构函数,导致内存泄漏
return 0;
}
如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄露。因此,如果一个类设计为基类,其析构函数通常应该声明为虚函数。
从内存层面理解析构函数
-
当对象销毁时:
- 栈上的对象:当栈帧弹出时,析构函数会被自动调用,对象占用的栈内存被释放。
- 堆上的对象:使用 delete 时,析构函数会被自动调用。
- 全局对象和静态对象:程序结束时,析构函数会被自动调用。
-
调用顺序:
- 对于类的对象成员,析构顺序与构造顺序相反(后构造的先析构)
- 对于继承关系,派生类的析构函数先调用,然后是基类的析构函数
-
内存回收与析构函数的关系:
析构函数负责的是资源清理工作,而不是对象本身所占内存的回收。对象所占内存的回收是在析构函数执行完毕后,有运行时系统自动完成的。
这个过程可以分为两个阶段:- 调用析构函数:执行用户定义的清理代码
- 回收对象内存:由运行时系统自动完成
编译器自动合成的析构函数
如果没有显示声明析构函数,编译器会自动合成一个默认的析构函数,这个合成的析构函数通常是“平方的”,意味着它不会执行任何实质性的操作:
编译器合成的析构函数按照成员声明的相反顺序调用所有非静态对象成员的析构函数,然后再调用基类的析构函数。
编译器生成的析构函数不会自动释放动态分配的内存资源或其他资源,因此,如果需要释放资源,需要显式地定义析构函数的执行逻辑。
class AutoGenerated {
std::string name; // 具有自己的析构函数的成员
int value; // 内置类型,不需要析构
};
// 编译器会生成类似于以下的析构函数:
// ~AutoGenerated() {
// name.~string(); // 调用string的析构函数
// // value不需要析构
// }
构造与析构的完整deme
#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>
#include <memory>
using namespace std;
class Resource
{
private:
string name;
weak_ptr<int> data;
ofstream logFile;
public:
/**
* @brief 构造函数,获取资源
*/
Resource(const string& name, int size) : name(name), data(make_shared<int>(size))
{
cout << "构造函数:创建资源 '" << name << "'" << endl;
logFile.open(name + ".log");
if(!logFile)
{
delete[] data.lock().get();
throw runtime_error("无法打开日志文件");
}
logFile << "开始日志" << endl;
}
/**
* @brief 析构函数,释放资源
*/
~Resource() noexcept
{
cout << "析构函数:释放资源 '" << name << "'" << endl;
try
{
// 关闭文件
if(logFile.is_open())
{
logFile << "资源被销毁" << endl;
logFile.close();
}
}
catch(...)
{
cerr << "文件关闭时出错" << endl;
// 捕获异常但不再抛出
}
delete[] data.lock().get();
cout << "资源 '" << name << "' 已完全释放" << endl;
}
// 禁止复制和赋值操作,以简化资源管理
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
};
class Container
{
private:
weak_ptr<Resource> resource;
public:
Container() : resource(make_shared<Resource>("ContainerResource", 1024))
{
cout << "创建容器" << endl;
}
~Container()
{
cout << "销毁容器" << endl;
delete resource.lock().get();
cout << "容器已完全释放" << endl;
}
};
int main(int argc, char const *argv[])
{
std::cout << "程序开始" << std::endl;
try {
std::cout << "\n--- 创建栈上的Resource对象 ---" << std::endl;
{
Resource r1("StackResource", 10);
std::cout << "Resource对象在使用中..." << std::endl;
} // r1的析构函数在这里自动调用
std::cout << "\n--- 创建堆上的Resource对象 ---" << std::endl;
Resource* r2 = new Resource("HeapResource", 20);
std::cout << "Resource对象在使用中..." << std::endl;
delete r2; // 显式调用析构函数
std::cout << "\n--- 通过Container管理Resource ---" << std::endl;
{
Container c;
std::cout << "Container对象在使用中..." << std::endl;
} // Container的析构函数自动调用,它会释放内部的Resource
} catch (const std::exception& e) {
std::cerr << "发生异常: " << e.what() << std::endl;
}
std::cout << "\n程序结束" << std::endl;
return 0;
}