析构函数可以理解为 “对象的清理工”,专门负责在对象 “消失” 时收拾残局。就像你离开房间前要关灯、关门一样,对象在销毁前也可能需要释放资源(比如关闭文件、释放内存、断开连接等)。
一、什么是析构函数?
析构函数是类的一种特殊函数,名字和类名相同,前面加~,没有参数,也没有返回值。
当对象 “寿终正寝” 时(比如超出作用域、被删除),析构函数会自动调用,帮你做收尾工作。
class FileHandler {
private:
FILE* mFile; // 打开的文件指针(需要手动关闭)
public:
// 构造函数:创建对象时打开文件
FileHandler(const std::string& filename) {
mFile = fopen(filename.c_str(), "r"); // 打开文件
std::cout << "文件已打开" << std::endl;
}
// 析构函数:对象销毁时关闭文件
~FileHandler() {
if (mFile != nullptr) {
fclose(mFile); // 关闭文件(释放资源)
std::cout << "文件已关闭" << std::endl;
}
}
};
二、什么时候需要写析构函数?
当类持有 “需要手动释放的资源” 时,必须写析构函数。常见的这类资源有:
- 动态分配的内存(用new创建的对象 / 数组)
- 打开的文件、网络连接、数据库连接
- 锁、句柄等系统资源
反之,如果类只持有基本类型(int、string等),不需要手动释放资源,就不用写析构函数(编译器会自动生成一个默认的,啥也不做)。
三、析构函数的用法示例
场景 1:释放动态内存(最常见)
class Student {
private:
char* mName; // 动态分配的字符串(需要手动释放)
public:
// 构造函数:分配内存
Student(const char* name) {
mName = new char[strlen(name) + 1]; // 分配内存
strcpy(mName, name);
std::cout << "创建学生:" << mName << std::endl;
}
// 析构函数:释放内存
~Student() {
delete[] mName; // 释放动态分配的数组
std::cout << "学生对象已销毁" << std::endl;
}
};
int main() {
{
Student s("小明"); // 创建对象,构造函数调用
// ... 使用对象
} // 超出作用域,析构函数自动调用,释放mName内存
return 0;
}
运行结果:
创建学生:小明
学生对象已销毁 // 析构函数自动执行
场景 2:避免资源泄漏(对比示例)
如果不写析构函数,会发生什么?
class BadFileHandler {
private:
FILE* mFile;
public:
BadFileHandler(const std::string& filename) {
mFile = fopen(filename.c_str(), "r");
}
// 没有析构函数!
};
int main() {
{
BadFileHandler f("test.txt"); // 打开文件
} // 对象销毁,但文件没关闭!资源泄漏!
return 0;
}
此时文件会一直处于 “打开” 状态,直到程序结束,这在长时间运行的程序(如服务器、APP)中会导致严重问题。
四、析构函数的关键特性
自动调用:不需要手动调用,对象销毁时自动执行。
只调用一次:一个对象的生命周期中,析构函数只会被调用一次。
继承中的析构函数:如果是父类,析构函数要加virtual(虚析构函数),否则子类的析构函数可能不会被调用(参考之前的虚函数示例)。
总结
析构函数是 “清理工具”,负责释放对象持有的资源。
必须写的情况:类里有new分配的内存、打开的文件 / 连接等需要手动释放的资源。
不用写的情况:类里只有int、string(自带析构)等基础类型。
那用智能指针就不用析构函数了呗?
可以这么理解,但不完全准确。更严谨的说法是:智能指针能减少手动写析构函数的需求,但不能完全替代析构函数。
举个具体例子你就明白了:
场景 1:只用智能指针管理内存 → 确实不用写析构函数
如果类里的资源只有 “动态内存”,且全部用智能指针管理,这时不需要手动写析构函数。
#include <memory>
class Student {
private:
// 用智能指针管理动态内存(自动释放)
std::unique_ptr<char[]> mName;
public:
Student(const char* name) {
// 智能指针自动分配内存
mName = std::make_unique<char[]>(strlen(name) + 1);
strcpy(mName.get(), name);
}
// 不需要写析构函数!
// 因为智能指针会自动释放mName的内存,编译器默认生成的析构函数足够用
};
这里智能指针已经 “包办” 了内存释放,所以不用手动写析构函数。
场景 2:有其他资源(文件、连接等)→ 仍然需要析构函数
如果类里除了内存,还有其他资源(比如打开的文件),即使有智能指针,也需要析构函数来释放这些资源。
#include <memory>
#include <cstdio>
class Student {
private:
std::unique_ptr<char[]> mName; // 智能指针管理内存
FILE* mFile; // 其他资源(文件)
public:
Student(const char* name, const char* filename) {
mName = std::make_unique<char[]>(strlen(name) + 1);
strcpy(mName.get(), name);
mFile = fopen(filename, "r"); // 打开文件
}
// 必须写析构函数!释放文件资源
~Student() {
if (mFile != nullptr) {
fclose(mFile); // 关闭文件(智能指针管不了这个)
}
}
};
这里智能指针只能管好mName的内存,但mFile这个文件句柄需要析构函数来释放。
核心区别:智能指针管 “内存”,析构函数管 “其他资源”
智能指针的本职工作:自动释放它所指向的动态内存(也就是用new分配的内存)。
析构函数的本职工作:释放类里所有需要手动清理的资源,包括但不限于:
文件、网络连接、数据库连接(这些智能指针管不了)
手动分配的内存(如果没用到智能指针的话)
锁、信号量等系统资源
总结
如果类里的所有动态内存都用智能指针管理,且没有其他需要手动释放的资源 → 不用写析构函数。
只要类里有智能指针管不了的资源(文件、连接等)→ 仍然需要写析构函数。
智能指针是 “内存管理小助手”,但不是 “万能管家”,析构函数在处理非内存资源时依然不可替代。
374

被折叠的 条评论
为什么被折叠?



