⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 1056.html

📁 著名的linux英雄站点的文档打包
💻 HTML
📖 第 1 页 / 共 5 页
字号:
currenthandler = p;<br>
return oldhandler;<br>
}<br>
<br>
最后看看x的operator new所做的:<br>
1. 调用标准set_new_handler函数,输入参数为x的出错处理函数。这使得x的new-handler函数成为全局new-handler函数。注意下面的代码中,用了"::"符号显式地引用std空间(标准set_new_handler函数就存在于std空间)。<br>
<br>
2. 调用全局operator new分配内存。如果第一次分配失败,全局operator new会调用x的new-handler,因为它刚刚(见1.)被安装成为全局new-handler。如果全局operator new最终未能分配到内存,它抛出std::bad_alloc异常,x的operator new会捕捉到它。x的operator new然后恢复最初被取代的全局new-handler函数,最后以抛出异常返回。<br>
<br>
3. 假设全局operator new为类型x的对象分配内存成功,, x的operator new会再次调用标准set_new_handler来恢复最初的全局出错处理函数。最后返回分配成功的内存的指针。<br>
c++是这么做的:<br>
<br>
<br>
void * x::operator new(size_t size)<br>
{<br>
new_handler globalhandler = // 安装x的new_handler<br>
std::set_new_handler(currenthandler);<br>
<br>
void *memory;<br>
<br>
try { // 尝试分配内存<br>
memory = ::operator new(size);<br>
}<br>
<br>
catch (std::bad_alloc&) { // 恢复旧的new_handler<br>
std::set_new_handler(globalhandler);<br>
throw; // 抛出异常<br>
}<br>
std::set_new_handler(globalhandler); // 恢复旧的new_handler<br>
return memory;<br>
}<br>
<br>
如果你对上面重复调用std::set_new_handler看不顺眼,可以参见条款m9来除去它们。<br>
<br>
使用类x的内存分配处理功能时大致如下:<br>
<br>
<br>
void nomorememory();// x的对象分配内存失败时调用的new_handler函数的声明<br>
<br>
x::set_new_handler(nomorememory);<br>
// 把nomorememory设置为x的<br>
// new-handling函数<br>
x *px1 = new x;<br>
// 如内存分配失败,<br>
// 调用nomorememory<br>
string *ps = new string;<br>
// 如内存分配失败,调用全局new-handling函数<br>
<br>
x::set_new_handler(0);<br>
// 设x的new-handling函数为空<br>
<br>
x *px2 = new x;<br>
// 如内存分配失败,立即抛出异常<br>
// (类x没有new-handling函数)<br>
<br>
你会注意到,处理以上类似情况,如果不考虑类的话,实现代码是一样的,这就很自然地想到在别的地方也能重用它们。正如条款41所说明的,继承和模板可以用来设计可重用代码。在这里,我们把两种方法结合起来使用,从而满足了你的要求。<br>
<br>
你只要创建一个“混合风格”(mixin-style)的基类,这种基类允许子类继承它某一特定的功能——这里指的是建立一个类的new-handler的功能。之所以设计一个基类,是为了让所有的子类可以继承set_new_handler和operator new功能,而设计模板是为了使每个子类有不同的currenthandler数据成员。这听起来很复杂,不过你会看到代码其实很熟悉。区别只不过是它现在可以被任何类重用了。<br>
<br>
<br>
template // 提供类set_new_handler支持的<br>
class newhandlersupport { // 混合风格”的基类<br>
public:<br>
static new_handler set_new_handler(new_handler p);<br>
static void * operator new(size_t size);<br>
<br>
private:<br>
static new_handler currenthandler;<br>
};<br>
<br>
template<br>
new_handler newhandlersupport::set_new_handler(new_handler p)<br>
{<br>
new_handler oldhandler = currenthandler;<br>
currenthandler = p;<br>
return oldhandler;<br>
}<br>
<br>
template<br>
void * newhandlersupport::operator new(size_t size)<br>
{<br>
new_handler globalhandler =<br>
std::set_new_handler(currenthandler);<br>
void *memory;<br>
try {<br>
memory = ::operator new(size);<br>
}<br>
catch (std::bad_alloc&) {<br>
std::set_new_handler(globalhandler);<br>
throw;<br>
}<br>
<br>
std::set_new_handler(globalhandler);<br>
return memory;<br>
}<br>
// this sets each currenthandler to 0<br>
<br>
template<br>
new_handler newhandlersupport::currenthandler;<br>
有了这个模板类,对类x加上set_new_handler功能就很简单了:只要让x从newhandlersupport继承:<br>
// note inheritance from mixin base class template. (see<br>
// my article on counting objects for information on why<br>
// private inheritance might be preferable here.)<br>
class x: public newhandlersupport {<br>
<br>
... // as before, but no declarations for<br>
}; // set_new_handler or operator new<br>
<br>
<br>
使用x的时候依然不用理会它幕后在做些什么;老代码依然工作。这很好!那些你常不去理会的东西往往是最可信赖的。<br>
<br>
使用set_new_handler是处理内存不够情况下一种方便,简单的方法。这比把每个new都包装在try模块里当然好多了。而且, newhandlersupport这样的模板使得向任何类增加一个特定的new-handler变得更简单。“混合风格”的继承不可避免地将话题引入到多继承上去,在转到这个话题前,你一定要先阅读条款43。<br>
<br>
1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[]——见条款8)以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:<br>
<br>
<br>
class widget { ... };<br>
<br>
widget *pw1 = new widget;// 分配失败抛出std::bad_alloc if<br>
<br>
if (pw1 == 0) ... // 这个检查一定失败<br>
widget *pw2 = new (nothrow) widget; // 若分配失败返回0<br>
<br>
if (pw2 == 0) ... // 这个检查可能会成功<br>
<br>
不管是用“正规”(即抛出异常)形式的new还是“无抛出”形式的new,重要的是你必须为内存分配失败做好准备。最简单的方法是使用set_new_handler,因为它对两种形式都有用。<br>
<br>
4.写operator new和operator delete时要遵循常规<br>
<br>
自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致。实际做起来也就是:要有正确的返回值;可用内存不够时要调用出错处理函数(见条款7);处理好0字节内存请求的情况。此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题。<br>
<br>
有关返回值的部分很简单。如果内存分配请求成功,就返回指向内存的指针;如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常。<br>
<br>
但事情也不是那么简单。因为operator new实际上会不只一次地尝试着去分配内存,它要在每次失败后调用出错处理函数,还期望出错处理函数能想办法释放别处的内存。只有在指向出错处理函数的指针为空的情况下,operator new才抛出异常。<br>
<br>
另外,c++标准要求,即使在请求分配0字节内存时,operator new也要返回一个合法指针。(实际上,这个听起来怪怪的要求确实给c++语言其它地方带来了简便)<br>
<br>
这样,非类成员形式的operator new的伪代码看起来会象下面这样:<br>
void * operator new(size_t size) // operator new还可能有其它参数<br>
{<br>
<br>
if (size == 0) { // 处理0字节请求时,<br>
size = 1; // 把它当作1个字节请求来处理<br>
}<br>
while (1) {<br>
分配size字节内存;<br>
<br>
if (分配成功)<br>
return (指向内存的指针);<br>
<br>
// 分配不成功,找出当前出错处理函数<br>
new_handler globalhandler = set_new_handler(0);<br>
set_new_handler(globalhandler);<br>
<br>
if (globalhandler) (*globalhandler)();<br>
else throw std::bad_alloc();<br>
}<br>
}<br>
<br>
处理零字节请求的技巧在于把它作为请求一个字节来处理。这看起来也很怪,但简单,合法,有效。而且,你又会多久遇到一次零字节请求的情况呢?<br>
<br>
你又会奇怪上面的伪代码中为什么把出错处理函数置为0后又立即恢复。这是因为没有办法可以直接得到出错处理函数的指针,所以必须通过调用set_new_handler来找到。办法很笨但也有效。<br>
<br>
条款7提到operator new内部包含一个无限循环,上面的代码清楚地说明了这一点——while (1)将导致无限循环。跳出循环的唯一办法是内存分配成功或出错处理函数完成了条款7所描述的事件中的一种:得到了更多的可用内存;安装了一个新的new -handler(出错处理函数);卸除了new-handler;抛出了一个std::bad_alloc或其派生类型的异常;或者返回失败。现在明白了为什么new-handler必须做这些工作中的一件。如果不做,operator new里面的循环就不会结束。<br>
<br>
很多人没有认识到的一点是operator new经常会被子类继承。这会导致某些复杂性。上面的伪代码中,函数会去分配size字节的内存(除非size为0)。size很重要,因为它是传递给函数的参数。但是大多数针对类所写的operator new(包括条款10中的那种)都是只为特定的类设计的,不是为所有的类,也不是为它所有的子类设计的。这意味着,对于一个类x的operator new来说,函数内部的行为在涉及到对象的大小时,都是精确的sizeof(x):不会大也不会小。但由于存在继承,基类中的operator new可能会被调用去为一个子类对象分配内存:<br>
class base {<br>
public:<br>
static void * operator new(size_t size);<br>
...<br>
};<br>
<br>
class derived: public base // derived类没有声明operator new<br>
{ ... }; //<br>
<br>
derived *p = new derived; // 调用base::operator new<br>
<br>
如果base类的operator new不想费功夫专门去处理这种情况——这种情况出现的可能性不大——那最简单的办法是把这个“错误”数量的内存分配请求转给标准operator new来处理,象下面这样:<br>
void * base::operator new(size_t size)<br>
{<br>
if (size != sizeof(base)) // 如果数量“错误”,让标准operator new<br>
return ::operator new(size); // 去处理这个请求<br>
//<br>
<br>
... // 否则处理这个请求<br>
}<br>
<br>
“停!”我听见你在叫,“你忘了检查一种虽然不合理但是有可能出现的一种情况——size有可能为零!”是的,我没检查,但拜托下次再叫出声的时候不要这么文绉绉的。:)但实际上检查还是做了,只不过融合到size != sizeof(base)语句中了。c++标准很怪异,其中之一就是规定所以独立的(freestanding)类的大小都是非零值。所以sizeof(base)永远不可能是零(即使base类没有成员),如果size为零,请求会转到::operator new,由它来以一种合理的方式对请求进行处理。(有趣的是,如果base不是独立的类,sizeof(base)有可能是零,详细说明参见"my article on counting objects")。<br>
<br>
如果想控制基于类的数组的内存分配,必须实现operator new的数组形式——operator new[](这个函数常被称为“数组new”,因为想不出"operator new[]")该怎么发音)。写operator new[]时,要记住你面对的是“原始”内存,不能对数组里还不存在的对象进行任何操作。实际上,你甚至还不知道数组里有多少个对象,因为不知道每个对象有多大。基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存,而子类对象往往比基类要大。所以,不能想当然认为base::operator new[]里的每个对象的大小都是sizeof(base),也就是说,数组里对象的数量不一定就是(请求字节数)/sizeof(base)。关于operator new[]的详细介绍参见条款m8。<br>
<br>
重写operator new(和operator new[])时所有要遵循的常规就这些。对于operator delete(以及它的伙伴operator delete[]),情况更简单。所要记住的只是,c++保证删除空指针永远是安全的,所以你要充分地应用这一保证。下面是非类成员形式的operator delete的伪代码:<br>
void operator delete(void *rawmemory)<br>
{<br>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -