在 C++ 中,动态创建一个新的对象所采用的常规语法是 Object* obj = new Object();
这种形式。这种形式的 new 表达式 在编译器生成代码时会执行两个操作:
- 调用 operator new 函数分配原始内存(功能如 malloc);
- 调用 Object 类的构造函数 Object::Object()。
如下面的例子:
#include <string>
#include <iostream>
class Object
{
public:
Object(int n)
: n(n)
{
std::cout << "Object() with n=" << n << std::endl;
}
~Object()
{
std::cout << "~Object() with n=" << n << std::endl;
}
void dump() const
{
std::cout << "Object @ " << this << " with n=" << n << std::endl;
}
protected:
int n;
};
int main()
{
Object* obj1 = new Object(626);
obj1->dump();
delete obj1;
}
将有类似下面的输出:
Object() with n=626
Object @ 00DAEF60 with n=626
~Object() with n=626
于是,这种形式的 new 有一个明显的缺陷:无法手动管理内存的分配,重载 operator new 可是强烈不建议的。
由于构造函数无法手动调用,如果我们需要手动管理 new 时的内存分配,应该怎么办呢?
这就会用到一种所谓的 placement new 语法:new (placement) Object();。
这种形式的语法可不常见。其中括号内的 placement 即是我们手动指定的内存地址。 这种形式的 new 不会自动申请内存,而是直接使用我们提供的(不管值是多少,NULL 也是可以的),所以我们需要保证内存有效,并且大小合适(不能小于对象的字节数)。 然后在该内存上调用对象的构造函数来构造对象。由于内存是我们提供的,我们将不能直接 delete 掉该对象。而是 手动调用析构函数,然后手动回收内存(如果有必要)。
举个例子:
int main()
{
// 在栈上分配至少 sizeof(Object) 大小的空间
char mem[sizeof(Object)];
std::cout << "mem: " << (void*)&mem[0] << std::endl;
std::cout << "---------------------------------------" << std::endl;
// 然后采用 placement new 语法在此内存上构造对象
Object* obj1 = new (mem) Object(220);
// 打印 obj 的地址,可以看到它就是 mem 的地址
std::cout << "obj: " << obj1 << std::endl;
obj1->dump();
// 内存不是 new 创建的,不要直接调用 delete!
// 而是手动调用析构函数来销毁对象,由于是栈上内存,让其自动释放
obj1->~Object();
std::cout << "---------------------------------------" << std::endl;
// 调用析构函数后,只是对象被析构了,但内存还是在的
// 可以直接在该内存上再次构造另一个对象
Object* obj2 = new (mem) Object(626);
obj2->dump();
obj2->~Object();
std::cout << "---------------------------------------" << std::endl;
// 当然,手动分配内存也是可以的
void* p = new char[sizeof(Object)];
Object* obj3 = new (p) Object(123);
obj3->dump();
obj3->~Object();
// 这个内存就需要我们自己释放了,因为是自己申请的
delete[] p;
}
将有类似下面的输出,注意它们的地址全部是一样的:
mem: 008FFA08
---------------------------------------
Object() with n=220
obj: 008FFA08
Object @ 008FFA08 with n=220
~Object() with n=220
---------------------------------------
Object() with n=626
Object @ 008FFA08 with n=626
~Object() with n=626
---------------------------------------
Object() with n=123
Object @ 02D95B28 with n=123
~Object() with n=123
例子也举了几个了,话说 placement new 用在什么场合?
一言以蔽之:需要手动管理内存的地方!。比如:
- 对象内存池,要知道,频繁分配小内存是会导致内存碎片严重,而且分配效率非常低下;
- 标准库,标准库就大量运用了内存池,不过它有另外一个名字:Allocator。
这篇文章的内容已被作者标记为“过时”/“需要更新”/“不具参考意义”。