在 C++ 中,析构函数通常由编译器在对象生命周期结束时自动调用:
-
栈对象离开作用域时;
-
堆对象被
delete时; -
临时对象表达式结束时。
但有时我们需要显式调用析构函数,即手动执行 obj.~Type() 或 ptr->~Type()。这通常发生在对象的内存管理与生命周期管理分离的场景,例如 placement new、自定义容器、内存池等。
1. 显式调用析构函数的语法
cpp
obj.~Type(); // 对栈对象或引用 ptr->~Type(); // 对指针指向的对象 obj.~decltype(obj)(); // 当类型名不方便写出时(C++11 起) std::destroy_at(ptr); // C++17 提供的更安全的方式
调用后,对象被销毁(其析构函数执行),但内存并不会被释放。如果对象是 new 出来的,还需要手动 operator delete 释放内存;如果是 placement new 在预分配内存上构造的,则只需调用析构函数,内存由外部管理。
2. 关键注意事项
-
不要对普通栈对象显式调用析构函数:当对象离开作用域时,编译器会自动再次调用析构函数,导致双重析构,这是未定义行为(UB)。
-
显式调用后,对象生命周期结束:再访问该对象(调用成员函数、读写成员)都是 UB。
-
显式调用析构函数 ≠
delete:delete会先调用析构函数,再释放内存;显式调用只销毁对象,不释放内存。 -
典型安全用法:用
placement new在已分配的内存上构造对象,之后必须显式调用析构函数来销毁对象,否则对象持有的资源(如动态内存、文件句柄)会泄漏。
#include <iostream>
#include <new> // placement new
#include <cstddef> // alignas, size_t
class MyClass {
public:
MyClass() {
std::cout << "MyClass 构造函数被调用\n";
}
~MyClass() {
std::cout << "MyClass 析构函数被调用\n";
}
void sayHello() const {
std::cout << "Hello from MyClass\n";
}
};
int main() {
// 1. 准备一块足够大且对齐的原始内存(栈上数组)
alignas(MyClass) char buffer[sizeof(MyClass)];
std::cout << "--- 第一次构造 ---\n";
// 2. 在 buffer 上使用 placement new 构造对象
MyClass* obj = new (buffer) MyClass();
obj->sayHello();
// 3. 显式调用析构函数,销毁对象(但 buffer 内存仍存在)
obj->~MyClass();
// 此时不能再使用 obj->sayHello(),因为对象已销毁
std::cout << "--- 第二次构造 ---\n";
// 4. 重新在同一块内存上构造新对象
MyClass* obj2 = new (buffer) MyClass();
obj2->sayHello();
// 5. 再次显式析构
obj2->~MyClass();
std::cout << "--- 程序结束 ---\n";
// buffer 是栈数组,离开 main 时自动释放,无需手动释放内存
return 0;
}

312

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



