operator new与placement new¶
new expression 实际上完成了两项工作:
- 分配内存
- 在分配的内存上执行初始化(类的构造函数)
可以通过订制operator new和placement new来控制内存分配。
new operator¶
int n = new int(10);
上面的程序片段中,new,即:new operator (expression)。实际上进行了两项工作:
- 分配一块原始的内存,operator new的完成的工作
- 用10来初始化第一步分配的内存。(调用对象的构造函数初始化内存)
operator new和placement new¶
operator new用于分配一块原始内存。其功能等同于malloc,其形式为:
void * operator new(size_t sz);
参数size_t sz表示分配内存的多少。
new operator (expression)调用operator new是隐式传入第一个参数size_t sz的。同样也可以像调用普通函数一样调用new operator,例如:
void * buff = operator new(sizeof(std::string));
operator new将会返回一块可以容纳std::string的原始内存。同样,也可以重载operator new,但是第一个参数类型必须是size_t。事实上, placement new是operator new的一种特定形式,其在标准库中定义为:
void * operator new(size_t sz, void * buff)
{
return buff;
}
通过placement new可以控制对象存放位置。最简单的,通常new产生的对象都是在heap上的,但是通过placement new可以在stack上new新的对象。
例如:
#include <iostream>
#include <cstdlib>
class Widget
{
public:
Widget(int32_t n) : m_data(n) {}
~Widget() {}
void show()
{
std::cout << m_data << std::endl;
}
private:
int32_t m_data;
};
void test()
{
// 在stack上构造一个Widget对象
int value = 1000;
Widget* pWidget = operator new(&value) Widget(1);
pWidget->show();
// 将引发segment fault
// delete pWidget;
}
再联想一下,内存的分配、初始化、释放都是要消耗时间的,如果可以将不使用的内存,重新回收利用,就能节省分配和释放的时间,对于一些场景是非常具有诱惑力的。如memcache, redis等内存数据库。
所以利用operator new和placement new可以使用一个简单的内存复用的内存池。
但是通过operator new,palcement new来“手动”管理内存,需要程序员非常小心,而且,一旦出现问题,可能无法通过第三方工具检测内存泄漏等问题。比较安全的一种做法是,一块内存中只保存一种对象类型,而且严禁进行类型转换。
内存释放¶
对于使用placement new产生的对象,应该谨慎使用delete来销毁之。因为无法确定内存的来源,如果内存是stack上的,使用delete会引发segment fault(如上面的例子);如果是在heap上,不会出错。不过为了一致性和安全性,对于palcement new产生的对象,应该直接调用对象的析构函数,另外销毁原始内存。
总结¶
new operator | 分配内存并初始化(heap-based) |
operator new: | 分配一块原始内存(heap) |
placement new: | 可以控制内存生成的位置 |
关于new/delete更详细的讨论,请参阅参数资料。
ACKNOWLEGMENT¶
参数资料¶
[1] | Effective C++(3rd), E16, E49-E52 |
[2] | More Effective C++,E8 |
[3] | C++ Primer(5th, 中文版) |
[4] | Counting Objects in C++, Scott Meyers, C/C++ Users Journal, April 1998 |