分享
 
 
 

C++ Gotchas 条款62:替换Global New和Global Delete

王朝c/c++·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Gotcha #62: Replacing Global New and Delete

Gotcha条款62:替换Global New和Global Delete

将operator new、operator delete、array new亦或array delete的标准global版本替换为自定制版本,这几乎从来都不是个好主意——即使C++标准允许你这么做。这些函数的标准版本一般都针对通用目的(general-purpose)之存储管理做了极大优化,而用户自定义的替代版本则不大会做得更好了。(然而,针对特定的类别或类别阶层体系采用(自定制的)成员函数形式的操作来定制其内存管理,则通常是合理的。)

如果operator new和operator delete针对特定目的之实现版本作出了与标准版本相异的行为,其就可能引入臭虫,因为许多标准程序库和第三方程序库的正确性皆依赖于这些函数缺省的标准实现版本。

比较安全的方案是对global版本的operator new等函数进行重载,而不是替代它们。假设我们要以特定的字符样式(character pattern)填充新分配的存储空间:

void *operator new( size_t n, const string &pat ) {

char *p = static_cast<char *>(::operator new( n ));

const char *pattern = pat.c_str();

if( !pattern || !pattern[0] )

pattern = "\0"; // note: two null chars

const char *f = pattern;

for( int i = 0; i < n; ++i ) {

if( !*f )

f = pattern;

p[i] = *f++;

}

return p;

}

该operator new版本接收一个字串样式作为引数,并将其拷贝到新分配的存储空间中。经由重载解析,编译器就可以区分标准operator new与我们自己的“接收两个引数之版本”。

string fill( "<garbage>" );

string *string1 = new string( "Hello" ); // 标准版本

string *string2 =

new (fill) string( "World!" ); // 重载的版本

标准中还定义了一个重载的operator new版本;除了以size_t作为第一引数之外,该版本还接收一个void*型别作为第二引数。该实现只是简单的返回第二引数。(其中的throw()语法是一个exception-specification(异常规范),意味该函数不会传播出任何异常。在后述讨论及一般情况下,都可以安然忽略之。)

void *operator new( size_t, void *p ) throw()

{ return p; }

这就是标准的placement new,用于在特定的位置空间建构一个对象。(其不同之处在于,标准的“单引数operator new”可以被替换,而试图替换placement new则是非法的。)本质上来说,我们会将其用于“让编译器误以为调用了一个构造函数”的场合。比如说,对于一个嵌入式应用,我们或许想在某个特定的硬件地址上建构一个“status register(状态寄存器)”对象:

class StatusRegister {

// . . .

};

void *regAddr = reinterpret_cast<void *>(0XFE0000);

// . . .

// 在regAddr的位置放一个register object

StatusRegister *sr = new (regAddr) StatusRegister;

自然,经由placement new创建的对象必须在某个时刻被销毁。然而,由于placement new并未真正的分配内存(译注:其只是在指定位置放入对象,并未进行内存分配),因此也必须保证在销毁时没有内存被删除。回忆一下,delete operator的行为是:在调用operator delete函数(以便归还存储空间)之前,首先唤起“欲删除对象”之析构函数。对于“对象是经由placement new进行‘空间分配’”的情形,为了避免任何尝试归还内存空间的动作,我们在销毁对象时必须对析构函数进行显式的(explicit)调用(译注:这正是delete operator所做的第一步操作,第二步“调用operator delete函数”的操作就不用去做了)。

sr->~StatusRegister(); // 显式的调用dtor, 不调用operator delete函数

Placement new和explicit destruction(显式析构操作)显然是非常有用的特性,但倘若不保守并谨慎的使用它们,显然也是非常危险的。(详见Gotcha条款47中一个来自标准程序库的例子。)

应注意,当我们重载operator delete时,这些重载版本绝不会被“使用标准delete形式的表达式”唤起。

void *operator new( size_t n, Buffer &buffer ); // 重载版本的new

void operator delete( void *p,

Buffer &buffer ); // 对应的重载版本之delete

// . . .

Thing *thing1 = new Thing; // 使用标准的operator new

Buffer buf;

Thing *thing2 = new (buf) Thing; // 使用重载版本的operator new

delete thing2; // 不对, 应该使用重载版本的delete

delete thing1; // 正确, 使用标准的operator delete

相应的,对于经由placement new创建的对象,我们不得不显式的(explicitly)调用该对象的析构函数,然后直接明了的调用适当的operator delete函数,以便显式的将对象的存储空间进行去配:

thing2->~Thing(); // 正确, 销毁Thing

operator delete( thing2, buf ); // 正确, 使用重载版本的delete

实际当中,经由“global operator new之重载版本”分配的存储空间经常错误的经由“global operator new之标准版本”被去配。一个避免这种错误的方法是保证:任何经由“global operator new之重载版本”分配的存储空间都是经由“global operator new之标准版本”来获取存储空间(译注:意即,在“global operator new之重载版本”的实现中,通过调用“global operator new之标准版本”来获取空间,详见本条款开头的示例)。这正是前述第一个重载实现版本(译注:指的正是本条款开头那个“以特定的字符样式(character pattern)填充新分配的存储空间”的例子)所用的方法,其能与“global operator delete之标准版本”相配合并运作正常:

string fill( "<garbage>" );

string *string2 = new (fill) string( "World!" );

// . . .

delete string2; // 运作正常!

一般来说,global operator new的重载版本要么就不分配任何存储空间,要么就应该只简单的包裹(wrap)global operator new的标准版本(译注:如本条款开头那个例子所示,重载版本是标准版本的一个wrapper)。

通常情况下,最好的方案是全然避免对“处于global scope的内存管理operator functions”做手脚,代之以“operator new、operator delete、array new、array delete的成员函数版本”来定制类别或类别阶层体系的内存管理操作。

在Gotcha条款61的结尾我们提到过,若从new表达式中的初始化操作传出一个异常,运行期系统会唤起一个“适当的”operator delete函数:

Thing *tp = new Thing( arg );

如果Thing的分配动作成功了但构造函数抛出异常,那么运行期系统将会唤起一个适当的operator delete函数来归还tp所指向的未经初始化的内存。在上例中,这个“适当的operator delete”要么是global版本的operator delete(void*),要么就是一个具有相同形式的成员函数版本。然而不同的operator new即意味着不同的operator delete:

Thing *tp = new (buf) Thing( arg );

此时,适当的operator delete应该是“双引数版本”operator delete(void*,Buffer&),与Thing分配操作所使用的“operator new之重载版本”相对应;这正是运行期系统会唤起的版本。

C++在定义内存管理的行为方面给予了颇大的弹性,伴之以复杂性作为代价。标准的“global operator new”和“global operator delete”便足以满足多数需求。因此,我们应该仅在确实需要的情况下才采用更复杂的方案。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有