
在 C++ 中,对象的生命周期管理是语言的核心特性之一。通常,析构函数(Destructor)由编译器自动调用,例如:
delete释放时。但在某些特殊场景下,需要显式调用析构函数(Explicit Destructor Call),例如:
本文将深入讲解显式析构函数调用的语法规则、应用场景、常见误区。
显式调用析构函数的语法非常直接:
对象实例.~类名();其中:
对象实例是类的实例(可以是指针、引用或直接对象)。类名是对象所属的类类型。析构函数的核心作用是释放对象持有的资源(如堆内存、文件句柄、网络连接等)。显式调用析构函数的本质是手动触发这一资源释放过程,但不会自动释放对象的内存(除非配合delete操作)。
特性 | 隐式调用(编译器自动触发) | 显式调用(手动触发) |
|---|---|---|
触发时机 | 对象生命周期结束时(栈对象离域、delete堆对象等) | 手动调用~ClassName() |
内存释放 | 栈对象:自动回收;堆对象:delete触发内存释放 | 不自动释放内存(需手动管理) |
资源释放 | 自动执行析构函数逻辑 | 手动执行析构函数逻辑 |
重复调用风险 | 无(编译器保证仅调用一次) | 可能重复调用(导致未定义行为) |

背景:定位 new(Placement New)允许在已分配的原始内存上构造对象,但不会自动释放内存。因此,当对象不再需要时,必须显式调用析构函数释放资源,之后手动释放内存(否则会导致资源泄漏)。
代码示例:定位 new 的显式析构
#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;
}运行结果 :

raw_memory或堆内存new char[...])。obj->~ResourceHolder(),data指向的堆内存不会被释放,导致资源泄漏。背景:内存池(Memory Pool)通过预先分配大块内存,避免频繁调用malloc/free,提升性能。当内存池中的对象被销毁时,需要显式调用析构函数释放资源,然后将内存块归还内存池(而非直接释放)。
代码示例:内存池中的显式析构
#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;
}运行结果:

id),仅管理内存块;对象的资源释放由析构函数完成。背景:某些情况下,需要提前释放对象持有的资源(如关闭文件、断开网络连接),但保留对象的内存以便后续重用。此时可以显式调用析构函数释放资源,之后通过定位 new 重新构造对象。
代码示例:资源的提前释放与重用
#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;
}运行结果

背景:如果对象的构造函数抛出异常,编译器会自动调用已构造成员的析构函数。但在某些复杂场景(如自定义内存管理)中,可能需要显式调用析构函数来处理未完成构造的对象。
代码示例:构造异常时的显式析构
#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转换为对象类型,但需确保对象已部分构造(否则可能导致未定义行为)。 错误示例
#include <iostream>
class Test {
public:
~Test() {
std::cout << "Test 析构" << std::endl;
}
};
int main() {
Test obj; // 栈对象
obj.~Test(); // 显式调用析构函数
// 栈对象离开作用域时,编译器会再次调用析构函数
return 0;
}运行结果(未定义行为)

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