new operator,这个操作符是由语言内建的,不能被改变意义,总是做相同的事情。它的动作分为两方面:
第一,它分配足够的内存,用来放置某类型的对象。
第二,它调用一个constructor,为刚才分配的内存中的那个对象设定初值。
你能够改变的是用来容纳对象的那块内存的分配行为。
new operator调用某个函数,执行必要的内存分配动作,你可以重写或重载那个函数,改变其行为。这个函数的名称叫做operator new。
函数operator new通常声明如下:
void * operator new(size_t size);
其返回值类型是void*。此函数返回一个指针,指向一块原始的、未设初值的内存。函数中的size_t参数表示需要分配多少内存,你可以将operator new重载,加上额外的参数,但第一参数的类型必须总是size_t。
你可以像调用任何其他函数一样地调用它:
void *rawMemory=operator new(sizeof(string));
这里的operator new将返回指针,指向一块足够容纳一个string对象的内存。
和malloc一样,operator new的唯一任务就是分配内存。取得operator new返回的内存并将之转换为一个对象,是new operator的责任。当你的编译器看到这样一个句子:
string *ps=new string("Memory Management");
它必须产生一些代码,或多或少会反映以下行为:
void *memory=operator new(sizeof(string)); //取得原始内存(raw memory)用来放置一个string对象
call string::string("Memory Management") on *memory; //将内存中的对象初始化
string *ps=static_cast<string*>(memory); //让ps指向新完成的对象
Placement new(特殊版本的operator new)
下面示范如何使用placement new:
class Widget
{
public:
Widget(int widgetSize);
..
}
Wdiget * constructWidgetInBuffer(void *buffer,int widgetSize)
{
return new (buffer) Widget(widgetSize);
}
此函数返回指针,指向一个Widget object,它被构造于传递给此函数的一块内存缓冲区上。当程序运行到shared memory或memory-mapped I/O,这类函数可能是有用的,因为在那样的运用中,对象必须置于特定地址,或是置于以特殊函数分配出来的内存上。
对于表达式new (buffer) Widget(widgetSize)
这是new operator的用法之一,其中指定一个额外自变量(buffer)作为new operator“隐式调用operator new”时所用。于是,被调用的operator new除了接受“一定得有的size_t自变量”之外,还接受了一个void*参数,指向一块内存,准备用来接受构造好的对象。这样的operator new就是所谓的placement new,看起来像这样:
void * operator new(size_t, void *location)
{
return location;
}
看起来很简单,毕竟operator new的目的是要为对象找到一块内存,然后返回一个指针指向它。在placement new的情况下,调用者已经知道指向内存的指针了,因为调用者知道对象应该放在哪里。因此placement new唯一需要做的就是将它获得的指针再返回。至于没有用到(但一定得有)的size_t参数,之所以不赋予名称,为的是避免编译器发出“某物未被使用”的警告。placement new是C++标准程序库的一部分。欲使用placement new,你必须用#include 。
如果你希望将对象产生于heap,请使用new operator。它不但分配内存而且为该对象调用一个constructor。
如果你只是打算分配内存,请调用operator new,那就没有任何constructor会被调用。
如果你打算在heap objects产生时自己决定内存分配方式,请写一个自己的operator new,并使用new operator,它将会自动调用你所写的operator new。
如果你打算在已分配(并拥有指针)的内存中构造对象,请使用placement new。
删除与内存释放
为了避免资源泄漏,每一个动态分配行为都必须匹配一个相应但相反的释放动作。函数operator delete对于内建的delete operator,就好像operator new对于new operator一样。
当你写出这样的代码:
string *ps;
...
delete ps;
它必须既能够析构ps所指对象,又能释放被该对象占用的内存。
内存释放动作是由函数operator delete执行,通常声明如下:
void operator delete(void *memoryToBeDeallocated)
因此,下面这个动作:delete ps;
会造成编译器产生近似这样的代码:
ps->~string();
operator delete(ps);
这里呈现的一个暗示就是,如果你只是打算处理原始的、未设初值的内存,应该完全回避new operator和delete operator,改调用operator new取得内存并以operator delete归还给系统。
这组行为在C++中相当于调用malloc和free。
如果你使用placement new,在某内存块中产生对象,你应该避免对那块内存使用delete operator。因为delete operator会调用operator delete来释放内存,但是该内存内含的对象最初并非是由operator new分配得来的。毕竟placement new只是返回它所接受的指针而已,谁知道那个指针从哪里来呢?所以为了抵消该对象的constructor的影响,你应该直接调用该对象的destructor:
//以下函数用来分配及释放shared memory中的内存
void * mallocShared(size_t size);
void freeShared(void *memory);
void *sharedMemory=mallocShared(sizeof(Widget));
Widget *pw=constructWidgetInBuffer(sharedMemory,10);
...
delete pw; //无定义!
//因为sharedMemory来自mallocShared,不是来自operator new
pw->~Widget(); //可以!析构pw所指的Widget对象,
//但并未释放Widget占用的内存。
freeShared(pw); //可以!释放pw所指的内存,不调用任何destructor
如此例所示,如果交给placement new的原始内存本身是动态分配而得,那么你最终还是得释放那块内存,以免遭受内存泄漏之苦。
数组
string *ps=new string[10]; //分配一个对象数组
由于诞生的是数组,所以new operator的行为与先前产生单一对象的情况略有不同,由一个名为operator new[]的函数负责分配。和operator new一样,operator new[]也可以被重载。
“数组版”与“单一对象版”的new operator的第二个不同是,它所调用的constructor数量。数组版new operator必须针对数组中的每个对象调用一个constructor。
同样道理,当delete operator被用于数组,它会针对数组中的每个元素调用其destructor,然后再调用operator delete[]释放内存。
就好像你可以取代或重载operator delete一样,你也可以取代或重载operator delete[]。不过两者的重载有着相同的限制。