副标题#e#
人们有时仿佛喜欢存心使C++语言的术语难以领略。好比说new操纵符(new operator)和operator new的区别。
当你写这样的代码:
string *ps = new string("Memory Management");
你利用的new是new操纵符。这个操纵符就象sizeof一样是语言内置的,你不能改变它的寄义,它的成果老是一样的。它要完成的成果分成两部门。第一部门是分派足够的内存以便容纳所需范例的工具。第二部门是它挪用结构函数初始化内存中的工具。new操纵符老是做这两件工作,你不能以任何方法改变它的行为。
你所能改变的是如作甚工具分派内存。new操纵符挪用一个函数来完成必须的内存分派,你可以或许重写或重载这个函数来改变它的行为。new操纵符为分派内存所挪用函数的名字是operator new。
函数operator new 凡是这样声明:
void * operator new(size_t size);
返回值范例是void*,因为这个函数返回一个未经处理惩罚(raw)的指针,未初始化的内存。(假如你喜欢,你能写一种operator new函数,在返回一个指针之前可以或许初始化内存以存储一些数值,可是一般不这么做。)参数size_t确定分派几多内存。你能增加特另外参数重载函数operator new,可是第一个参数范例必需是size_t。(有关operator new更多的信息拜见Effective C++ 条款8至条款10。)
你一般不会直接挪用operator new,可是一旦这么做,你可以象挪用其它函数一样挪用它:
void *rawMemory = operator new(sizeof(string));
操纵符operator new将返回一个指针,指向一块足够容纳一个string范例工具的内存。
就象malloc一样,operator new的职责只是分派内存。它对结构函数一无所知。operator new所相识的是内存分派。把operator new 返回的未经处理惩罚的指针通报给一个工具是new操纵符的事情。当你的编译器碰见这样的语句:
#p#副标题#e#
string *ps = new string("Memory Management");
它生成的代码或多或少与下面的代码相似(更多的细节见Effective C++条款8和条款10,尚有我的文章Counting object里的注释。):
void *memory = // 获得未经处理惩罚的内存
operator new(sizeof(string)); // 为String工具
call string::string("Memory Management") //初始化
on *memory; // 内存中
// 的工具
string *ps = // 是ps指针指向
static_cast<string*>(memory); // 新的工具
留意第二步包括告终构函数的挪用,你做为一个措施员被克制这样去做。你的编译器则没有这个约束,它可以做它想做的一切。因此假如你想成立一个堆工具就必需用new操纵符,不能直接挪用结构函数来初始化工具。
Placement new
有时你确实想直接挪用结构函数。在一个已存在的工具上挪用结构函数是没有意义的,因为结构函数用来初始化工具,而一个工具仅仅能在给它初值时被初始化一次。可是有时你有一些已经被分派可是尚未处理惩罚的的(raw)内存,你需要在这些内存中结构一个工具。你可以利用一个非凡的operator new ,它被称为placement new。
下面的例子是placement new如何利用,思量一下:
class Widget {
public:
Widget(int widgetSize);
...
};
Widget * constructWidgetInBuffer(void *buffer,
int widgetSize)
{
return new (buffer) Widget(widgetSize);
}
这个函数返回一个指针,指向一个Widget工具,工具在转递给函数的buffer里分派。当措施利用共享内存或memory-mapped I/O时这个函数大概有用,因为在这样措施里工具必需被安排在一个确定地点上或一块被例程分派的内存里。(拜见条款4,一个如何利用placement new的一个差异例子。)
在constructWidgetInBuffer内里,返回的表达式是:
new (buffer) Widget(widgetSize)
这初看上去有些生疏,可是它是new操纵符的一个用法,需要利用一个特另外变量(buffer),当new操纵符隐含挪用operator new函数时,把这个变量通报给它。被挪用的operator new函数除了待有强制的参数size_t外,还必需接管void*指针参数,指向结构工具占用的内存空间。这个operator new就是placement new,它看上去象这样:
void * operator new(size_t, void *location)
{
return location;
}
这大概比你期望的要简朴,可是这就是placement new需要做的工作。究竟operator new的目标是为工具分派内存然后返回指向该内存的指针。在利用placement new的环境下,挪用者已经得到了指向内存的指针,因为挪用者知道工具应该放在那边。placement new必需做的就是返回转递给它的指针。(没有用的(可是强制的)参数size_t没有名字,以防备编译器发出告诫说它没有被利用;见条款6。) placement new是尺度C++库的一部门。为了利用placement new,你必需利用语句#include <new>(可能假如你的编译器还不支持这新气势气魄的头文件名)。
#p#分页标题#e#
让我们从placement new返来半晌,看看new操纵符(new operator)与operator new的干系,你想在堆上成立一个工具,应该用new操纵符。它既分派内存又为工具挪用结构函数。假如你仅仅想分派内存,就应该挪用operator new函数;它不会挪用结构函数。假如你想定制本身的在堆工具被成立时的内存分派进程,你应该写你本身的operator new函数,然后利用new操纵符,new操纵符会挪用你定制的operator new。假如你想在一块已经得到指针的内存里成立一个工具,应该用placement new。
Arrays
到今朝为止一切顺利,可是还得接着走。到今朝为止我们所测试的都是一次成立一个工具。奈何分派数组?会产生什么?
string *ps = new string[10]; // allocate an array of
// objects
被利用的new仍然是new操纵符,可是成立数组时new操纵符的行为与单个工具成立有少许差异。第一是内存不再用operator new分派,取代以等同的数组分派函数,叫做operator new[](常常被称为array new)。它与operator new一样能被重载。这就答允你节制数组的内存分派,就象你能节制单个工具内存分派一样(可是有一些限制性说明,拜见Effective C++ 条款8)。
(operator new[]对付C++来说是一个较量新的对象,所以你的编译器大概不支持它。假如它不支持,无论在数组中的工具范例是什么,全局operator new将被用来给每个数组分派内存。在这样的编译器下定制数组内存分派是坚苦的,因为它需要重写全局operator new。这可不是一个能等闲接管的任务。缺省环境下,全局operator new处理惩罚措施中所有的动态内存分派,所以它行为的任何改变都将有深入和普遍的影响。并且全局operator new有一个正常的签名(normal signature)(也就单一的参数size_t,拜见Effective C++条款9),所以假如你 抉择用本身的要领声明它,你立即使你的措施与其它库不兼容基于这些思量,在缺乏operator new[]支持的编译器里为数组定制内存打点不是一个公道的设计。)
第二个差异是new操纵符挪用结构函数的数量。对付数组,在数组里的每一个工具的结构函数都必需被挪用:
string *ps = // 挪用operator new[]为10个
new string[10]; // string工具分派内存,
// 然后对每个数组元素挪用
// string工具的缺省结构函数。
同样当delete操纵符用于数组时,它为每个数组元素挪用析构函数,然后挪用operator delete来释放内存。
就象你能替换或重载operator delete一样,你也替换或重载operator delete[]。在它们重载的要领上有一些限制。请参考优秀的C++课本。
new和delete操纵符是内置的,其行为不受你的节制,每每它们挪用的内存分派和释放函数则可以节制。当你想定制new和delete操纵符的行为时,请记着你不能真的做到这一点。你只能改变它们为完成它们的成果所采纳的要领,而它们所完成的成果则被语言牢靠下来,不能改变。(You can modify how they do what they do, but what they do is fixed by the language)