placement new 要求缓冲区地址满足 alignof(T) 对齐,否则行为未定义;需手动调用析构函数,禁用 delete;推荐用 std::aligned_storage_t 和 std::launder 保障安全。

placement new 的基本用法和内存对齐要求
直接在字符数组上调用 placement new 是可行的,但前提是该数组地址满足目标类型的对齐要求。C++ 标准规定,new (ptr) T 要求 ptr 至少按 alignof(T) 对齐;否则行为未定义——哪怕构造函数没报错,后续访问也可能崩溃或读到垃圾值。
常见错误是直接拿 char buf[1024] 的首地址去构造 std::string 或自定义类,而没检查对齐。比如 x86-64 上 std::string 通常要求 8 字节对齐,但栈上数组起始地址可能只保证 16 字节(取决于编译器和调用上下文),不一定满足所有类型需求。
- 用
alignas(T) char buf[N]显式对齐缓冲区,最稳妥 - 或用
std::aligned_alloc(alignof(T), size)(C++17)动态分配对齐内存 - 若必须用普通
char[],可用std::align手动跳过前缀字节找对齐地址
如何安全地在 char 数组中构造对象并管理生命周期
placement new 只负责调用构造函数,不分配内存;因此你必须手动调用析构函数,且绝不能用 delete 或 delete[] 释放该内存——那会触发 operator delete,而你压根没用 operator new 分配它。
典型误操作:构造后直接离开作用域,忘了析构;或用 delete static_cast<t>(ptr)</t> 导致双重释放/崩溃。
立即学习“C++免费学习笔记(深入)”;
- 构造:
new (buf) MyClass(arg1, arg2) - 析构:
static_cast<myclass>(buf)->~MyClass()</myclass>(注意强制转换要准确) - 若缓冲区里存多个对象,每个都要单独调用对应位置的析构函数
- 建议把缓冲区封装成 RAII 类(如
aligned_storage+ 构造/析构逻辑),避免裸指针管理
std::aligned_storage 和 std::launder 的必要性
用原始 char[] 存对象时,C++17 前编译器可能因优化误判该内存未被“合法使用”,导致读取结果异常。C++17 引入 std::launder 就是为解决这个问题:它告诉编译器“这块内存现在确实存放了某个类型的对象,请按对象语义访问”。
同时,std::aligned_storage_t<size align></size> 比手写 alignas(...) 更可靠——它自动选择满足大小和对齐的最优布局,且类型安全。
- 声明缓冲区:
std::aligned_storage_t<sizeof alignof> buf;</sizeof> - 构造:
auto p = new (static_cast<void>(&buf)) Foo();</void> - 访问:
Foo* ptr = std::launder(reinterpret_cast<foo>(&buf));</foo> - 析构:
ptr->~Foo();
常见错误:memcpy 替代 placement new、未处理异常、类型尺寸不足
有人试图用 memcpy(&buf, &obj, sizeof(obj)) 把已有对象“复制进去”,这完全错误:它绕过了构造函数,成员(尤其是虚表指针、std::string 内部指针等)不会被正确初始化,后续调用成员函数大概率崩溃。
另一个坑是没考虑异常安全:如果构造函数抛异常,placement new 不会自动回滚,但已执行的部分(如基类构造、成员初始化)可能已修改缓冲区状态,需确保构造函数本身是无异常的,或自行捕获并清理。
- 务必用
sizeof(T)验证缓冲区是否够大,alignof(T)验证是否对齐 - 禁止对非 trivially destructible 类型省略析构调用
- 多态对象(含虚函数)可原地构造,但切勿通过基类指针 delete 派生类实例——这里根本没 new,更不该 delete
真正麻烦的不是语法怎么写,而是对齐、生命周期、类型别名规则这三块边界稍有不慎就进未定义行为。别图省事跳过 std::launder 或 alignas,尤其在跨平台或开启 LTO 时。


















