目录
[1.1 语法格式](#1.1 语法格式)
[1.2 本质:手动触发资源释放逻辑](#1.2 本质:手动触发资源释放逻辑)
[1.3 与隐式调用的区别](#1.3 与隐式调用的区别)
[1.4 底层机制 编辑](#1.4 底层机制 编辑)
[2.1 场景 1:定位 new 构造的对象](#2.1 场景 1:定位 new 构造的对象)
[2.2 场景 2:自定义内存池中的对象管理](#2.2 场景 2:自定义内存池中的对象管理)
[2.3 场景 3:提前释放资源但保留对象内存](#2.3 场景 3:提前释放资源但保留对象内存)
[2.4 场景 4:操作未完成构造的对象(异常安全)](#2.4 场景 4:操作未完成构造的对象(异常安全))
[3.1 误区 1:对栈对象显式调用析构函数](#3.1 误区 1:对栈对象显式调用析构函数)
[3.2 误区 2:对堆对象仅显式析构而不释放内存](#3.2 误区 2:对堆对象仅显式析构而不释放内存)
[3.3 误区 3:对智能指针管理的对象显式析构](#3.3 误区 3:对智能指针管理的对象显式析构)
[4.1 仅在必要时使用显式析构](#4.1 仅在必要时使用显式析构)
[4.2 配合内存释放操作](#4.2 配合内存释放操作)
[4.3 避免重复析构](#4.3 避免重复析构)
[4.4 异常安全](#4.4 异常安全)
在 C++ 中,对象的生命周期管理是语言的核心特性之一。通常,**析构函数(Destructor)**由编译器自动调用,例如:
- 栈对象离开作用域时。
- 堆对象通过
delete
释放时。 - 临时对象完成表达式计算后。
但在某些特殊场景下,需要显式调用析构函数(Explicit Destructor Call),例如:
- 使用定位 new(Placement New)在已分配内存上构造对象时。
- 操作自定义内存池或资源管理类时。
- 需要提前释放资源(如文件句柄、网络连接)但保留对象内存时。
本文将深入讲解显式析构函数调用的语法规则 、应用场景 、常见误区。
一、显式析构函数调用的语法与本质
1.1 语法格式
显式调用析构函数的语法非常直接:
cpp
对象实例.~类名();
其中:
对象实例
是类的实例(可以是指针、引用或直接对象)。类名
是对象所属的类类型。
1.2 本质:手动触发资源释放逻辑
析构函数的核心作用是释放对象持有的资源 (如堆内存、文件句柄、网络连接等)。显式调用析构函数的本质是手动触发这一资源释放过程 ,但不会自动释放对象的内存(除非配合delete
操作)。
1.3 与隐式调用的区别
特性 | 隐式调用(编译器自动触发) | 显式调用(手动触发) |
---|---|---|
触发时机 | 对象生命周期结束时(栈对象离域、delete 堆对象等) |
手动调用~ClassName() |
内存释放 | 栈对象:自动回收;堆对象:delete 触发内存释放 |
不自动释放内存(需手动管理) |
资源释放 | 自动执行析构函数逻辑 | 手动执行析构函数逻辑 |
重复调用风险 | 无(编译器保证仅调用一次) | 可能重复调用(导致未定义行为) |
1.4 底层机制 
二、显式析构函数调用的核心场景
2.1 场景 1:定位 new 构造的对象
背景: 定位 new(Placement New)允许在已分配的原始内存 上构造对象,但不会自动释放内存。因此,当对象不再需要时,必须显式调用析构函数释放资源,之后手动释放内存(否则会导致资源泄漏)。
代码示例:定位 new 的显式析构
cpp
#include <iostream>
#include <new>
// 模拟需要管理资源的类
class ResourceHolder {
private:
int* data; // 模拟堆内存资源
public:
ResourceHolder(int size) {
data = new int[size];
std::cout << "ResourceHolder 构造:分配 " << size << " 个int的内存" << std::endl;
}
~ResourceHolder() {
delete[] data;
std::cout << "ResourceHolder 析构:释放堆内存" << std::endl;
}
void print() const {
std::cout << "资源地址:" << data << std::endl;
}
};
int main() {
// 1. 分配原始内存(64字节足够容纳ResourceHolder)
alignas(ResourceHolder) char raw_memory[sizeof(ResourceHolder)];
// 2. 使用定位new构造对象(在raw_memory上构造)
ResourceHolder* obj = new (raw_memory) ResourceHolder(100);
// 3. 使用对象
obj->print();
// 4. 显式调用析构函数(释放资源)
obj->~ResourceHolder();
// 5. 手动释放原始内存(此处raw_memory是栈内存,无需释放;若是堆内存需用delete[])
// 注意:若raw_memory是堆分配的(如new char[...]),需在此处调用delete[] raw_memory;
return 0;
}
运行结果 :

- 定位 new 的生命周期 :定位 new 仅构造对象,不分配内存。因此,对象的内存需要用户手动管理(如示例中的栈内存
raw_memory
或堆内存new char[...]
)。 - 显式析构的必要性 :若不调用
obj->~ResourceHolder()
,data
指向的堆内存不会被释放,导致资源泄漏。
2.2 场景 2:自定义内存池中的对象管理
背景: 内存池(Memory Pool)通过预先分配大块内存,避免频繁调用malloc
/free
,提升性能。当内存池中的对象被销毁时,需要显式调用析构函数释放资源,然后将内存块归还内存池(而非直接释放)。
代码示例:内存池中的显式析构
cpp
#include <iostream>
#include <vector>
#include <new>
// 内存池类(简化版)
class MemoryPool {
private:
char* pool; // 内存池起始地址
size_t block_size; // 每个内存块大小
size_t block_num; // 内存块数量
bool* used; // 记录内存块是否被使用
public:
MemoryPool(size_t block_size, size_t block_num)
: block_size(block_size), block_num(block_num) {
pool = new char[block_size * block_num];
used = new bool[block_num]{false};
}
void* allocate() {
for (size_t i = 0; i < block_num; ++i) {
if (!used[i]) {
used[i] = true;
return pool + i * block_size;
}
}
return nullptr; // 内存池已满
}
void deallocate(void* p) {
if (p < pool || p >= pool + block_size * block_num) return;
size_t index = (static_cast<char*>(p) - pool) / block_size;
used[index] = false;
}
~MemoryPool() {
delete[] pool;
delete[] used;
}
};
// 需要内存池管理的类
class PooledObject {
private:
int id;
public:
PooledObject(int id) : id(id) {
std::cout << "PooledObject " << id << " 构造" << std::endl;
}
~PooledObject() {
std::cout << "PooledObject " << id << " 析构" << std::endl;
}
void print() const {
std::cout << "PooledObject " << id << " 正在运行" << std::endl;
}
};
int main() {
MemoryPool pool(sizeof(PooledObject), 5); // 内存池:5个块,每个块容纳PooledObject
// 从内存池分配内存并构造对象
std::vector<PooledObject*> objects;
for (int i = 0; i < 3; ++i) {
void* mem = pool.allocate();
if (!mem) break;
PooledObject* obj = new (mem) PooledObject(i); // 定位new构造
objects.push_back(obj);
}
// 使用对象
for (auto obj : objects) {
obj->print();
}
// 显式析构并归还内存池
for (auto obj : objects) {
obj->~PooledObject(); // 显式调用析构函数
pool.deallocate(obj); // 归还内存块
}
return 0;
}
运行结果:

- 内存池的核心逻辑:内存池负责分配和回收原始内存,对象的构造和析构由用户通过定位 new 和显式析构完成。
- 资源管理的解耦 :内存池不关心对象的资源(如
id
),仅管理内存块;对象的资源释放由析构函数完成。
2.3 场景 3:提前释放资源但保留对象内存
背景: 某些情况下,需要提前释放对象持有的资源(如关闭文件、断开网络连接),但保留对象的内存以便后续重用。此时可以显式调用析构函数释放资源,之后通过定位 new 重新构造对象。
代码示例:资源的提前释放与重用
cpp
#include <iostream>
#include <new>
#include <fstream>
// 模拟文件管理类
class FileHandler {
private:
std::fstream file; // 文件流
public:
FileHandler(const std::string& filename) {
file.open(filename, std::ios::out | std::ios::in);
if (file.is_open()) {
std::cout << "文件 " << filename << " 打开成功" << std::endl;
} else {
std::cerr << "文件 " << filename << " 打开失败" << std::endl;
}
}
~FileHandler() {
if (file.is_open()) {
file.close();
std::cout << "文件关闭" << std::endl;
}
}
void write(const std::string& content) {
if (file.is_open()) {
file << content;
}
}
};
int main() {
// 分配原始内存(足够容纳FileHandler)
alignas(FileHandler) char mem[sizeof(FileHandler)];
// 第一次构造:打开文件
FileHandler* fh1 = new (mem) FileHandler("test.txt");
fh1->write("第一次写入");
fh1->~FileHandler(); // 显式关闭文件(释放资源)
// 第二次构造:重用内存,重新打开文件
FileHandler* fh2 = new (mem) FileHandler("test.txt");
fh2->write("第二次写入");
fh2->~FileHandler(); // 显式关闭文件
return 0;
}
运行结果

- 内存重用:通过显式析构释放资源后,原始内存可以重复用于构造新的对象(减少内存分配次数)。
- 资源生命周期控制:析构函数的显式调用允许精确控制资源的释放时机(如在写入完成后立即关闭文件)。
2.4 场景 4:操作未完成构造的对象(异常安全)
**背景:**如果对象的构造函数抛出异常,编译器会自动调用已构造成员的析构函数。但在某些复杂场景(如自定义内存管理)中,可能需要显式调用析构函数来处理未完成构造的对象。
代码示例:构造异常时的显式析构
cpp
#include <iostream>
#include <new>
#include <stdexcept>
class ComplexObject {
private:
int* data;
int size;
public:
ComplexObject(int size) : size(size) {
data = new int[size];
std::cout << "分配 " << size << " 个int的内存" << std::endl;
// 模拟构造过程中抛出异常(如参数非法)
if (size <= 0) {
delete[] data; // 提前释放已分配的内存
throw std::invalid_argument("size必须大于0");
}
}
~ComplexObject() {
delete[] data;
std::cout << "释放 " << size << " 个int的内存" << std::endl;
}
void print() const {
std::cout << "数据地址:" << data << std::endl;
}
};
int main() {
// 分配原始内存
alignas(ComplexObject) char mem[sizeof(ComplexObject)];
try {
// 构造对象(size=0,触发异常)
ComplexObject* obj = new (mem) ComplexObject(0);
obj->print(); // 不会执行
} catch (const std::invalid_argument& e) {
std::cerr << "构造异常:" << e.what() << std::endl;
// 显式调用析构函数(即使构造未完成,仍需释放已分配的资源)
// 注意:此处obj可能未完全构造,需通过placement new的指针手动析构
// 实际中需确保obj指针有效(如构造函数在抛出前已初始化成员)
reinterpret_cast<ComplexObject*>(mem)->~ComplexObject();
}
return 0;
}
运行结果

- 异常安全 :即使构造函数抛出异常,已分配的资源(如
data
)仍需释放。显式调用析构函数可以确保这一点。 - 指针的有效性 :在异常处理中,
mem
的指针需要通过reinterpret_cast
转换为对象类型,但需确保对象已部分构造(否则可能导致未定义行为)。
三、显式析构函数调用的常见误区
3.1 误区 1:对栈对象显式调用析构函数
错误示例
cpp
#include <iostream>
class Test {
public:
~Test() {
std::cout << "Test 析构" << std::endl;
}
};
int main() {
Test obj; // 栈对象
obj.~Test(); // 显式调用析构函数
// 栈对象离开作用域时,编译器会再次调用析构函数
return 0;
}
运行结果(未定义行为)

错误原因: 栈对象的析构函数由编译器自动调用(离开作用域时)。显式调用会导致析构函数被重复执行,破坏对象的内存状态(如重复释放堆内存),引发未定义行为(如崩溃、数据损坏)。
3.2 误区 2:对堆对象仅显式析构而不释放内存
错误示例
cpp
#include <iostream>
class HeapObject {
private:
int* data;
public:
HeapObject() {
data = new int[100];
std::cout << "构造:分配堆内存" << std::endl;
}
~HeapObject() {
delete[] data;
std::cout << "析构:释放堆内存" << std::endl;
}
};
int main() {
HeapObject* obj = new HeapObject(); // 堆对象
obj->~HeapObject(); // 显式析构(释放data)
// 未调用delete obj; 导致内存泄漏
return 0;
}

内存泄漏分析:
new HeapObject()
分配了两部分内存:
HeapObject
对象本身的内存(由new
分配)。- 对象内部
data
指向的堆内存(由构造函数中的new int[100]
分配)。- 显式调用
obj->~HeapObject()
仅释放了data
的内存,但HeapObject
对象本身的内存未被释放(需通过delete obj
触发operator delete
释放)。
3.3 误区 3:对智能指针管理的对象显式析构
错误示例
cpp
#include <iostream>
#include <memory>
class SmartObj {
public:
~SmartObj() {
std::cout << "SmartObj 析构" << std::endl;
}
};
int main() {
auto ptr = std::unique_ptr<SmartObj>(new SmartObj());
// 无需显式调用析构函数(智能指针自动管理)
ptr->~SmartObj(); // 危险!重复析构
return 0; // ptr离开作用域时自动析构并释放内存
}
运行结果(未定义行为)

错误原因:
智能指针(如
std::unique_ptr
、std::shared_ptr
)会在生命周期结束时自动调用析构函数并释放内存。显式调用析构函数会导致资源被重复释放,引发未定义行为。
四、显式析构函数调用的最佳实践
4.1 仅在必要时使用显式析构
显式析构函数调用是一种低级内存管理技术,应仅在以下场景使用:
- 定位 new 构造的对象(必须手动析构)。
- 自定义内存池中的对象管理(内存由用户而非编译器管理)。
- 需要精确控制资源释放时机(如提前关闭文件、断开连接)。
4.2 配合内存释放操作
对于定位 new 构造的对象,显式析构后必须手动释放原始内存(如delete[] raw_memory
或归还内存池)。对于堆对象,显式析构后需调用delete
释放对象内存(但通常不建议这样做,应优先使用delete
触发自动析构)。
4.3 避免重复析构
- 栈对象、智能指针管理的对象、通过
delete
释放的堆对象,其析构函数已由编译器或智能指针自动调用,禁止显式调用。 - 自定义内存管理时,确保每个对象仅被析构一次(可通过标记位记录是否已析构)。
4.4 异常安全
若析构函数可能抛出异常(尽管 C++ 最佳实践建议析构函数不抛出异常),显式调用时需使用try-catch
块捕获异常,避免程序终止。
五、总结
显式析构函数调用是 C++ 中高级内存管理的重要工具,其核心价值在于手动控制资源释放时机。总结以下关键点:
场景 | 显式析构是否必要 | 配合操作 | 风险提示 |
---|---|---|---|
定位 new 构造的对象 | 是 | 手动释放原始内存 | 忘记析构导致资源泄漏 |
自定义内存池 | 是 | 归还内存块到内存池 | 重复析构导致未定义行为 |
提前释放资源 | 是 | 后续通过定位 new 重用内存 | 资源未完全释放 |
栈对象 / 智能指针对象 | 否 | 依赖编译器 / 智能指针自动析构 | 重复析构导致崩溃 |
合理使用显式析构函数调用,可以提升内存管理的灵活性和性能(如内存池、资源重用),但需严格遵循使用规范,避免未定义行为。在大多数情况下,应优先依赖编译器自动调用析构函数,仅在必要时使用显式调用。