placement new不可用普通new替代,因其仅调用构造函数而不分配内存,专用于已预分配内存(如内存池)的场景,需手动管理析构与对齐,且不配对delete。

placement new 为什么不能直接用普通 new 替代
普通 new 会做两件事:分配内存 + 调用构造函数。而 placement new 只负责第二步——在你已经拿好的内存块上“唤醒”对象。这在内存池场景里是刚需:内存池提前 malloc 一大块内存,后续所有对象都复用它,绝不触发系统级分配。
常见错误现象:new (ptr) MyClass() 编译失败?大概率忘了 #include <new></new>;或者 ptr 没对齐(比如 MyClass 要求 16 字节对齐,但你传的是 char* 偏移 3 字节的地址)。
- 必须确保
ptr地址满足目标类型的对齐要求,可用alignof(MyClass)查 - 不能对同一块内存反复调用 placement new 而不先调用析构函数——会泄漏资源、破坏状态
- placement new 不抛异常(除非你显式加
throw()),但构造函数本身仍可能 throw,此时需手动清理已构造的子对象
怎么用 placement new 实现一个最简内存池
核心就三步:预分配、取块、构造。重点不是“怎么写完”,而是“怎么避免越界和重叠”。
典型使用场景:高频创建销毁小对象(如游戏中的粒子、网络包解析器中的节点),且对象生命周期受控(比如全在帧结束时批量回收)。
立即学习“C++免费学习笔记(深入)”;
- 预分配用
malloc或aligned_alloc(C++17),别用new char[]——它不保证足够对齐 - 从池中取内存时,用指针算术移动偏移,每次跳过
sizeof(T) + alignof(T)的上界对齐值(推荐用std::align辅助) - 构造后必须保存原始指针(即 placement new 返回的地址),否则无法正确调用析构函数
示例:
char* pool = static_cast<char*>(aligned_alloc(alignof(Foo), 4096)); Foo* f = new (pool) Foo(); // 构造 f->~Foo(); // 必须显式析构
delete 和 placement new 是“半配对”的
没有 placement delete 这种东西。你用 placement new 构造,就得自己管析构 + 内存释放逻辑。这是最容易翻车的地方。
错误现象:对象析构了,但内存没归还给池;或者调用了 delete f(触发 operator delete),而 f 根本不是 new 分配的——直接 UB。
- 永远不要对 placement new 构造的对象用
delete或delete[] - 析构必须显式调用
f->~T(),且仅当对象已被成功构造(注意构造函数 throw 后的清理) - 内存归还给池,是池自己的事,和 C++ 的 delete 机制完全无关
对齐和异常处理是隐藏最深的坑
对齐不对,轻则性能掉一截,重则 CPU 直接报 bus error(尤其 ARM 或 AVX 类型);异常没兜住,会导致部分成员构造成功、部分失败,对象处于不可用状态。
性能影响:placement new 本身零开销,但如果你在循环里反复 std::align 计算对齐位置,又没缓存对齐后的起始地址,就会拖慢分配路径。
- 用
std::aligned_storage_t或std::aligned_alloc配合alignof,别靠手算 - 构造函数可能 throw?那就得在 placement new 外包一层 try-catch,并在 catch 里逆序析构已构造的成员(如果有的话)
- 多线程用内存池?分配/回收操作本身要加锁,但 placement new 这一步可以无锁——它只操作本地指针
真正麻烦的从来不是语法,是把“谁负责分配”“谁负责对齐”“谁负责析构”“谁负责归还”四件事,在代码里划清边界。边界模糊了,问题就藏在运行半年后的某个凌晨三点。










