placement new 是 operator new 的一个重载版本,日常开发中用到的机会并不多。如果你想在一段已经分配好的内存中创建对象,普通的 new 是做不到的。placement new 的作用正是允许你在一块已有的内存(无论来自栈还是堆)之上构造一个新的对象。它的原型中那个 void* p 参数,实际指向的就是这段已经准备好的内存缓冲区的首地址。
我们知道,普通 new 在分配内存时需要在堆中寻找足够大的空闲块,这个查找过程不仅慢,还可能因为空间不足而抛出异常。placement new 恰好能绕开这个问题:因为对象是直接在预先分配好的缓冲区中构造的,不再需要临时查找空间,内存分配的时间是常数级的,也不会在程序运行过程中突然冒出内存不足的异常。正因如此,placement new 非常适合那些对响应时间要求苛刻、并且希望长时间稳定运行不被打断的场景。
使用方法
1. 提前分配缓冲区
缓冲区既可以放在堆上,也可以放在栈上,对应两种分配方式:
class MyClass { ... };
// 堆上分配
char *buf = new char[N*sizeof(MyClass) + sizeof(int)];
// 或者栈上分配
char buf[N*sizeof(MyClass) + sizeof(int)];
2. 在缓冲区上构造对象
MyClass * pClass = new(buf) MyClass;
3. 销毁对象
当这个对象不再需要时,必须显式调用类的析构函数来销毁它。注意,此时只是对象被销毁,内存空间本身并不会被释放,以便后续在同一块缓冲区上继续构造其他对象。
pClass->~MyClass();
4. 释放内存
释放内存的方式取决于缓冲区分配在哪里:
- 如果缓冲区在堆上,调用
delete[] buf;进行释放。 - 如果缓冲区在栈上,它在所属作用域内一直有效,离开作用域时会自动回收,无需手动释放。
注意事项
- 在 C++ 标准中,对于 placement operator new[] 有如下说明:placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 也就是说,实现需要额外的空间来保存数组大小。因此我们在申请缓冲区时,必须比原始对象大小多预留 sizeof(int) 个字节,用来存放对象个数(数组大小)。
- 使用流程第 2 步中出现的 new 才是真正的 placement new。它并没有申请内存,只是调用了构造函数,并返回一个指向已有内存的指针。因此在销毁对象时不需要用 delete 释放空间,但必须显式调用析构函数来销毁对象本身。