不能用 delete 销毁 placement new 构造的对象,因为 delete 会错误地尝试释放未由 new 分配的内存,引发未定义行为;必须手动调用析构函数并按原始内存来源正确释放。

为什么不能用 delete 销毁 placement new 构造的对象
因为 delete 会先调用析构函数,再尝试释放内存——但 placement new 根本没分配堆内存,它只用了你传进去的 buffer 地址。如果用 delete objPtr,编译器会按常规路径去查 operator delete,最终可能尝试释放一个栈地址、静态缓冲区地址,或根本不是由 new 分配的内存块,直接触发未定义行为(常见表现:程序崩溃、double free 报错、ASan 检测失败)。
必须手动调用析构函数,且仅调用一次
placement new 构造的对象,生命周期管理完全交由你负责。销毁时唯一合法动作是显式调用析构函数:
-
objPtr->~MyClass()是标准写法,适用于单个对象 - 对数组,需逐个调用:
for (int i = 0; i - 不能重复调用,否则析构函数二次执行,可能 double-free 内部资源(如
std::string的堆内存) - 如果类有虚析构函数,仍只需调用
objPtr->~MyClass(),不必用objPtr->~Base()—— 虚析构已确保正确调用链
原始内存释放前,必须确保所有对象都已析构
原始内存(比如 char* buffer 或栈数组)的释放时机,必须严格晚于所有在其上构造的对象的析构完成:
- 若内存来自
new[] char[...],用delete[] buffer - 若来自
malloc,用free(buffer) - 若在栈上(如
alignas(T) char buf[sizeof(T)];),**不释放**——函数返回时自动回收,但必须在作用域结束前调用析构 - 若内存属于内存池或共享内存段,需按池管理协议归还,而非直接
free
容易被忽略的对齐与异常安全问题
即使析构逻辑写对了,仍可能因底层细节出错:
-
buffer必须满足目标类型的对齐要求,推荐用alignas(MyClass) char buffer[sizeof(MyClass)];,而不是裸char buffer[...];否则new (buffer) MyClass()可能触发未定义行为 - 如果构造函数抛异常,placement new 不会自动回滚——你得自己捕获并确保不遗漏析构(实际中建议用 RAII 封装,例如自定义
aligned_storage+ 析构代理) - 含虚函数表、继承关系复杂的类,析构顺序仍遵循 C++ 标准(派生类 → 基类),但你必须确保每个子对象都在其内存位置上被正确析构,不能跳过中间层
真正危险的不是“忘了调析构”,而是“以为 delete 能兜底”或者“在未对齐内存上调用析构”。这两点一旦出错,调试难度远高于普通内存泄漏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











