条款8:了解各种不同意义的new和delete

有时候我们觉得,C++的术语仿佛是要故意让人难以理解似的。

这里就有一个例子:请说明new operator 和operator new 之间的差异(译注:本书所说的new operator,即某些C++教程如C++ Primer 所谓的new expression)

当你写出这样的代码:

string *ps= new string("Memory Management");

你所使用的 new 是所谓的new operator。

这个操作符是由语言内建的,就像sizeof那样,不能被改变意义,总是做相同的事情。

它的动作分为两方面。

  1. 第一,它分配足够的内存,用来放督某类型的对象。以上例而言,它分配足够放置一个string 对象的内存。
  2. 第二,它调用一个constructor,为刚才分配的内存中的那个对象设定初值。

new operator总是做这两件事,无论如何你不能够改变其行为。

你能够改变的是用来容纳对象的那块内存的分配行为。也就是上面的第一步

new operator 调用某个函数,执行必要的内存分配动作你可以重写或重载那个函数,改变其行为。这个函数的名称叫做operator new。头昏了吗?真的,我说的是真的。

函数operator new 通常声明如下:

void * operator new(size_t size);

其返回值类型是void*。此函数返回一个指针,指向一块原始的、未设初值的内存(如果你喜欢,可以写一个新版的operator new,在其返回内存指针之前先将那块内存设定初值。只不过这种行为颇为罕见就是了)。

函数中的size_t 参数表示需要分配多少内存。

你可以将operator new 重载,加上额外的参数,但第一参数的类型必须总是 size_t(如何撰写 operator new,相关信息请参考条款E8~E10)

吸中协从不想到要直接调用operator new,但如果你要,你可以像调用任何其他函数一样地调用它:

void *rawMemory = operator new(sizeof(string));

这里的operator new将返回指针,指向一块足够容纳一个string对象的内存。

和malloc一样,operator new的唯一任务就是分配内存。它不知道什么是constructors, operator new只负责内存分配。

取得 operator new 返回的内存并将之转换为一个对象,是new operator 的责任。

当你的编译器看到这样一个句子:

string *ps= new string("Memory Management");

它必须产生一些代码,或多或少会反映以下行为(见条款E8和条款E10,以及发表于C/C++ Users Journal, April1998 的文章《Counting Objects inC++》中的方块内容)

void *memory =
operator new(sizeof(string));//取得原始内存(raw memory)。用来放置一个string对象。

call string::string("Memory Management")//将内存中的对象初始化。
on *memory


string *ps=
static_cast<string*>(memory);//让ps指向新完成的对象。

注意上述第二步骤涉及“调用一个constructor”,身为程序员的你没有权力这么做。

然而你的编译器百无禁忌,可以为所欲为。这就是为什么如果你想要做出一个heap-based object,一定得使用new operator 的原因:你无法直接调用“对象初始化所必需的constructor”
(尤其它可能得为重要成分vtbl设定初值,见条款24)。

Placement new

有时候你真的会想直接调用一个constructor。

针对一个已存在的对象调用其constructor 并无意义,因为 constructors 用来将对象初始化,而对象只能被初始化一次。

但是偶尔你会有一些分配好的原始内存,你需要在上面构建对象。有一个特殊版本的operator new,称为placement new,允许你那么做。
下面示范如何使用 placement new:

class Widget {
public:
widget(int widgetsize);
//...
}

widget * constructWidgetInBuffer (void *buffer, int widgetSize)
{
return new (buffer) Widget(widgetsize);
}

此函数返回指针,指向一个widget object,它被构造于传递给此函数的一块内存缓冲区上。

当程序运行到shared memory 或memory-mapped I/O,这类函数可能是有用的,因为在那样的运用中,对象必须置于特定地址,或是置于以特殊函数分配出来的内存上(条款4列有placement new的另一个运用实例)

在 constructWidgetInBuffer 函数内部,唯一一个表达式是,

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)//注意size_t后面没名字
{
return location;
}

似乎比你预期得更简单,但这便是placement new必须做的一切。

毕竟operator new的目的是要为对象找到一块内存,然后返回一个指针指向它。

在placement new的情况下,调用者已经知道指向内存的指针了,因为调用者知道对象应该放在哪里。因此placement new 唯一需要做的就是将它获得的指针再返回。

至于没有用到(但一定得有)的size_t参数,之所以不赋予名称,为的是避免编译器发出“某物未被使用”的警告(见条款6)Placement new 是C++标准程序库(见条款E49)的一部分。

欲使用 placement new,你必须用#include <new>。如果你的编译器尚未支持新式头文件名称的话(见条款E49),就用#include<new.h>。

花几分钟回头想想 placement new,我们便能了解 new operator 和 operator new之间的关系,两个术语虽然表面上令人迷惑,概念上却十分直接易懂。

  1. 如果你希望将对象产生于heap,请使用 new operator。它不但分配内存而且为该对象调用一个constructor。
  2. 如果你只是打算分配内存,请调用 operator new,那就没有任何constructor会被调用。
  3. 如果你打算在heap objects 产生时自己决定内存分配方式,请写一个自己的 operator new,并使用 new operator,它将会自动调用你所写的operator new。
  4. 如果你打算在已分配(并拥有指针)的内存中构造对象,请使用placement new(若想更深入地了解new和delete,请见条款E7及发表于C/C++Users Journal, April1998的文章《Counting Objects in C++》)

删除(Deletion)与内存释放(Deallocation)

为了避免resource leaks(资源泄漏),每一个动态分配行为都必须匹配一个相应但相反的释放动作。函数operator delete 对于内建的delete operator,就好像operator new对于new operator 一样。当你写出这样的代码:

string *ps;
//。。。
delete ps;// 使用 delete operator。


你的编译器必须产生怎样的代码?

它必须既能够析构 ps所指对象,又能够释放被该对象占用的内存。

内存释放动作是由函数operator delete 执行,通常声明如下:

void operator delete(void *memoryToBeDeallocated);

因此,下面这个动作:

delete ps;


会造成编译器产生近似这样的代码:

ps->~string();// 调用对象的dtoroperator。

operator delete (ps);//释放对象所占用的内存。

这里呈现的一个暗示就是,如果你只打算处理原始的、未设初值的内存,应该完全回避new operator 和 delete operators,改调用operator new取得内存并以operator delete 归还给系统:

void *buffer=operator new(50*sizeof(char));//分配足够的内存,放置50个 chars;没有调用任何ctors

operator delete(buffer);//释放内存,没有调用任何dtors。

这组行为在C++中相当于调用malloc和free。

如果你使用placement new,在某内存块中产生对象,你应该避免对那块内存使用delete opcrator.因为 delete operator会调用 operator delete来释放内存,但是该内存内含的对象最初并非是由 operator new分配得来的。

毕竟placemen new只是返回它所接收的指针而已,谁知道那个指针从哪里来呢?所以为了抵消该对象的constructor的影响,你应该直接调用该对象的destructor:

//以下函数用来分配及释放 shared memory 中的内存。
void* mallocshared(size_t size);
void freeShared(void *memory);

void*sharedMemory=mallocShared(sizeof(Widget));//和先前相同,运用

Widget*pw=constructWidgetInBuffer(sharedMemory, 10); // placement new。
//..
delete pw;//无定义!因为sharedMemory 来自mallocshared,不是来自 operator new。

pw->~Widget();
//可!析构 pw 所指的widget 对象,
//但并未释放widget占用的内存。

freeShared(pw);
//可!释放 pw所指的内存,
//不调用任何 destructor。

如此例所示,如果交给placement new 的原始内存(raw memory)本身是动态分配而得(通过某种非传统做法),那么你最终还是得释放那块内存,以免遭受内存泄漏(memory leak)之苦(请参考文章《Counting Objects inC++》之中的方块内容,其中对所谓的“placement delete”有些介绍)。

数组(Arrays)

目前为止一切都好,但我们还有更远的路要走。截至目前我们考虑的每件事情都只在单一对象身上打转。面对数组怎么办?下面会发生什么事情;

string *ps= new string[10);//分配一个对象数组

上述使用的new 仍然是那个new operator,但由于诞生的是数组,所以new operator的行为与先前产生单一对象的情况略有不同。

是的,内存不再以operator new分配,而是由其“数组版”兄弟,一个名为operator new[]的函数负责分配(通常被称为“aray new”)。

和operator new一样,operator new[]也可以被重载。这使你得夺取数组的内存分配权,就像你可以控制单一对象的内存分配一样(不过条款E8对此有些警告)。

operatornew[]是相当晚的时候才加入C++的一个特性,所以你的编译器或许尚未支持它。

如果是这样,全局operator new会被用来为每个数组分配内存一不论数组中的对象类型是什么。

在这样的编译器下定制“数组内存分配行为”很困难,因为你得改写全局版的 operator new才行。这可不是件容易的工作。

默认情况下全局版的operator new负责程序中所有的动态内存分配,所以其行为的任何改变都可能带来剧烈而普遍的影响。此外,全局版本的operator new,其正规形式的型构(eignature)(我的意思是,只有唯一size_t参数的那个,见条款E9)只有一个,所以如果你决定声称它为你所拥有,你的软件便立刻不容于任何做了相同决定的程序库(见条款 27)。

多方考虑之下,如果你面对的是尚未支持 。perator new[]的编译器,定制“数组内存管理行为”往往不是个理想的决定。

“数组版”与“单一对象版”的newoperator的第二个不同是,它所调用的constructor数量。数组版new operator 必须针对数组中的每个对象调用一个constructor:

string *ps =//调用operator new[]以分配足够容纳

new string[10];//10个 string 对象的内存,然后
//针对每个元素调用 string default ctor。

同样道理,当delete operator 被用于数组,它会针对数组中的每个元素调用其 destructor,然后再调用 operator delete[]释放内存:

delete [] ps;
//为数组中的每个元素调用 string dtor.

然后调用 operator delete[]以释放内存。

就好像你可以取代或重载 operator delete一样,你也可以取代或重载operator delete[]。不过两者的重载有着相同的限制。请你找一本好的C++教程,查阅其细节。说到好的C++教程,本书p285列有我的一份推荐名单。

现在,你有了完整的知识。

new operator 和delete operator 都是内建操作符,无法为你所控制,但是它们所调用的内存分配/释放函数则不然。

当你想要定制new operator 和 delete operator的行为,记住,你其实无法真正办到。你可以修改它们完成任务的方式,至于它们的任务,已经被语言规范固定死了。

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值