前言:众所周知,C++中的操作符new和delete是用来动态分配内存初始化对象和手动回收内存析构对象的。但这只是最基本最常用的用法。new和delete其实还有更内涵的东西。
1 区分各种不同的new
new操作符其实分为三种:new operator ,operator new ,以及placement new。
1.1 new operator
当我们写出这样的代码:
string*ps = new string("Memory Management");
这时所用的new正是newoperator。这个操作符是由语言内建的,就像sizeof那样,不能被改变意义,总是做相同的事情。它的动作分为两方面。第一,它分配足够的内存,用来放置某类型的对象。以上例而言,它分配足够放置一个string对象的内存。第二,它调用constructor,为刚才分配的内存中的那个对象设定初值。newoperator总是做这两件事。
1.2 operator new
呵呵,C++的述语好像是故意让人难以理解似的。没看错,就是operatornew.
operator new 通常声明如下:
void * operatornew (size_t size);
其返回值类型是void*.此函数返回一个指针,指向一块原始的,未设初值的内存。size表示分配多少内存。你可以将operatornew重载,加上额外的参数,但第一参数的类型必须总是size_t;或许你从未想到要直接调用operatornew,但如果你要,你可以像调用任何其他函数一样调用它:
void* rawMemory= operator new(sizeof(string));
这里的operatornew 将返回指针,指向一块足够容纳一个string对象的内存。
和malloc一样,operatornew的唯一任务就是分配内存。它不知道什么是constructor,operator new 只负责内存分配。取得operatornew 返回的内存并将之转换为一个对象,是new operator的责任。当你的编译器看到这样一个句子:
string *ps = newstring("Memory Management");
它必须产生一些代码,或多或少会反映以下行为:
void *memory =operator new(sizeof(string));
callstring::string("Memory Management") on *memory ;
string *ps =static_cast<string*>(memory);
也就是说,newoperator 其实先是调用operator new 来分配内存的,接着才是它自己要完成的初始化操作。
1.3 placement new
有时候你真的会想直接调用一个constructor。针对一个已存在的对象调用其constructor并无意义,因为constructor用来将对象初始化,而对象只能被初始化一次。但是偶尔你会有一些分配好的原始内存,你需要在上面构建对象。有一个特殊版本的operatornew,称为placement new,允许你这么做。
下面示范如何使用placementnew:
class Widget{
public:
Widget(int widgetSize)
......
};
Widget *constructWidgetInBuffer(void *buffer,int widgetSize);
{
returnnew(buffer) Widget(widgetSize);
}
此函数返回指针,指向一个Widgetobject,它被构造于传递给此函数的一块内存缓冲区上。当程序运行到sharedmemory或memory-mapped I/O,这类函数可能是有用的,因为在那样的运用中,对象必须置于特定地址,或是置于以特殊函数分配出来的内存上。
在constructorWidgetInBuffer函数内部,唯一一个表达式是:
new(buffer)Widget(widgetSize);
乍见之下有点奇怪,其实不足为奇,这只是newoperator 的用法之一,其中指定一个额外自变量(buffer)作为newoperator "隐式调用operator new "时所用。于是,被调用的operatornew除了接受一个"一定得有的size_t自变量"之外,还接受一个void*参数,指向一块内存,准备用来接受构造好的对象。这样的operatornew就是所谓的placement new。
总之:如果你希望将对象产生于heap,请使用newoperator。它不但分配内存而且为该对象调用一个constructor。如果你只是打算分配内存,请调用operatornew ,那就没有任何constructor会被调用。如果你打算在heap objects产生时自己决定内存分配方式,请写一个自己的operatornew ,并且使用new operator,它将会自动调用你所写的operatornew。如果你打算在已分配的(并拥有指针)的内存中构造对象,请使用placement new。
2区分各种delete
为了避免资源泄漏,每一个动态分配行为都必须匹配一个相应但相反的释放动作。操作符operatordelete对于内建的delete operator,就好像operatornew 对于new operator一样。当你写出这样的代码:
string *ps;
...
delete ps;
你的编译器必须产生怎么样的代码?它必须既能够析构出ps所指对象,又能够释放被该对象占用的内存。
内存释放动作是由operatordelete执行,通常声明如下:
void operatordelete(void *memoryToBeDeallocated);
因此,下面这个动作,会造成编译器产生近似这样的代码:
ps->~string(); //调用对象的dtoroperator
operatordelete(ps); //释放对象占用的内存。
这里呈现一个暗示就是,如果你只打算处理原始版的,未设初值的内存,应该完全回避newoperator和delete operator,改调用operatornew 取得内存并以operator delete 归还给系统:
void * buffer =operator new (50*sizeof(char));
//分配足够的内存,放置50 个chars;没有调用任何ctors
....
operatordelete(buffer);
//释放内存,没有调用任何dtors
这组行为相当于C中的malloc和free。
如果你使用了placementnew ,在某内存块中产生对象,你应该避免对那块内存使用delete operator。因为deleteoperator会调用operator delete 来释放内存,但是该内存内含的对象最初并非是由operatornew分配来的。毕竟placement new只管返回它所接收的指针而已,谁知道那个指针从哪里来呢?所以为了抵消该对象的constructor的影响,你应该直接调用该对象的destructor:
//以下函数用来分配及释放sharedmemeory 中的内存。
void *mallocShared(size_t size);
voidfreeShared(void *memory);
void*shareMemory = mallocShared(sizeof(Widget));
Widget *pw =constructWidgetInBuffer(shareMemory,10);
//和先前相同,这里运用placementnew。
delete pw;
//无定义!因为shareMemory来自mallocShared,不是来自operatornew.
pw->Widge();
//可!析构ps所指的Widget对象,但并未释放Widget占用的内存。
freeShared(pw);
//可!释放pw所指的内存
//不调用任何destructor.
如此例所示,如果交给placementnew 的原始内存(raw memory)本身是动态分配而得(通过某种非传统做法),那么你最终还是得释放那块内存,以免遭受内存泄漏之苦。