📄 1056.html
字号:
if (rawmemory == 0) return; file://如/果指针为空,返回<br>
//<br>
<br>
释放rawmemory指向的内存;<br>
<br>
return;<br>
}<br>
<br>
这个函数的类成员版本也简单,只是还必须检查被删除的对象的大小。假设类的operator new将“错误”大小的分配请求转给::operator new,那么也必须将“错误”大小的删除请求转给::operator delete:<br>
<br>
class base { // 和前面一样,只是这里声明了<br>
public: // operator delete<br>
static void * operator new(size_t size);<br>
static void operator delete(void *rawmemory, size_t size);<br>
...<br>
};<br>
<br>
void base::operator delete(void *rawmemory, size_t size)<br>
{<br>
if (rawmemory == 0) return; // 检查空指针<br>
<br>
if (size != sizeof(base)) { // 如果size"错误",<br>
::operator delete(rawmemory); // 让标准operator来处理请求<br>
return;<br>
}<br>
<br>
释放指向rawmemory的内存;<br>
<br>
return;<br>
}<br>
<br>
可见,有关operator new和operator delete(以及他们的数组形式)的规定不是那么麻烦,重要的是必须遵守它。只要内存分配程序支持new-handler函数并正确地处理了零内存请求,就差不多了;如果内存释放程序又处理了空指针,那就没其他什么要做的了。至于在类成员版本的函数里增加继承支持,那将很快就可以完成。<br>
5.避免隐藏标准形式的new <br>
因为内部范围声明的名称会隐藏掉外部范围的相同的名称,所以对于分别在类的内部<br>
和全局声明的两个相同名字的函数f来说,类的成员函数会隐藏掉全局函数:<br>
void f(); // 全局函数<br>
class x {<br>
public:<br>
void f(); // 成员函数<br>
};<br>
x x;<br>
<br>
f(); // 调用 f<br>
<br>
x.f(); // 调用 x::f<br>
<br>
这不会令人惊讶,也不会导致混淆,因为调用全局函数和成员函数时总是采用不同的<br>
<br>
语法形式。然而如果你在类里增加了一个带多个参数的operator new函数,结果就有<br>
<br>
可能令人大吃一惊。<br>
<br>
class x {<br>
public:<br>
void f();<br>
<br>
// operator new的参数指定一个<br>
// new-hander(new的出错处理)函数<br>
static void * operator new(size_t size, new_handler p);<br>
};<br>
<br>
void specialerrorhandler(); // 定义在别的地方<br>
<br>
x *px1 =<br>
new (specialerrorhandler) x; // 调用x::operator new<br>
<br>
x *px2 = new x; // 错误!<br>
<br>
在类里定义了一个称为“operator new”的函数后,会不经意地阻止了对标准new的访<br>
<br>
问。条款50解释了为什么会这样,这里我们更关心的是如何想个办法避免这个问题。<br>
<br>
一个办法是在类里写一个支持标准new调用方式的operator new,它和标准new做同样<br>
<br>
的事。这可以用一个高效的内联函数来封装实现。<br>
<br>
class x {<br>
public:<br>
void f();<br>
<br>
static void * operator new(size_t size, new_handler p);<br>
<br>
static void * operator new(size_t size)<br>
{ return ::operator new(size); }<br>
};<br>
<br>
x *px1 =<br>
new (specialerrorhandler) x; // 调用 x::operator<br>
// new(size_t, new_handler)<br>
<br>
x* px2 = new x; // 调用 x::operator<br>
// new(size_t)<br>
<br>
另一种方法是为每一个增加到operator new的参数提供缺省值(见条款24):<br>
<br>
class x {<br>
public:<br>
void f();<br>
<br>
static<br>
void * operator new(size_t size, // p缺省值为0<br>
new_handler p = 0); //<br>
};<br>
<br>
x *px1 = new (specialerrorhandler) x; // 正确<br>
<br>
x* px2 = new x; // 也正确<br>
<br>
无论哪种方法,如果以后想对“标准”形式的new定制新的功能,只需要重写这个函数。<br>
<br>
调用者重新编译链接后就可以使用新功能了。<br>
<br>
6. 如果写了operator new就要同时写operator delete<br>
<br>
让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?<br>
<br>
答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。<br>
<br>
例如有这样一个表示飞机的类:类airplane只包含一个指针,它指向的是飞机对象的实际描述(此技术在条款34进行说明):<br>
<br>
class airplanerep { ... }; // 表示一个飞机对象<br>
//<br>
class airplane {<br>
public:<br>
...<br>
private:<br>
airplanerep *rep; // 指向实际描述<br>
};<br>
<br>
一个airplane对象并不大,它只包含一个指针(正如条款14和m24所说明的,如果airplane类声明了虚函数,会隐式包含第二个指针)。但当调用operator new来分配一个airplane对象时,得到的内存可能要比存储这个指针(或一对指针)所需要的要多。之所以会产生这种看起来很奇怪的行为,在于operator new和operator delete之间需要互相传递信息。<br>
<br>
因为缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。有一种常用的方法可以让operator new来告诉operator delete当初分配的内存大小是多少,就是在它所返回的内存里预先附带一些额外信息,用来指明被分配的内存块的大小。也就是说,当你写了下面的语句,<br>
<br>
airplane *pa = new airplane;<br>
<br>
你不会得到一块看起来象这样的内存块:<br>
<br>
pa——> airplane对象的内存<br>
<br>
而是得到象这样的内存块:<br>
<br>
pa——> 内存块大小数据 + airplane对象的内存<br>
<br>
对于象airplane这样很小的对象来说,这些额外的数据信息会使得动态分配对象时所需要的的内存的大小翻番(特别是类里没有虚拟函数的时候)。<br>
<br>
如果软件运行在一个内存很宝贵的环境中,就承受不起这种奢侈的内存分配方案了。为airplane类专门写一个operator new,就可以利用每个airplane的大小都相等的特点,不必在每个分配的内存块上加上附带信息了。<br>
<br>
具体来说,有这样一个方法来实现你的自定义的operator new:先让缺省operator new分配一些大块的原始内存,每块的大小都足以容纳很多个airplane对象。airplane对象的内存块就取自这些大的内存块。当前没被使用的内存块被组织成链表——称为自由链表——以备未来airplane使用。听起来好象每个对象都要承担一个next域的开销(用于支持链表),但不会:rep 域的空间也被用来存储next指针(因为只是作为airplane对象来使用的内存块才需要rep指针;同样,只有没作为airplane对象使用的内存块才需要next指针),这可以用union来实现。<br>
<br>
具体实现时,就要修改airplane的定义,从而支持自定义的内存管理。可以这么做:<br>
<br>
class airplane { // 修改后的类 — 支持自定义的内存管理<br>
public: //<br>
<br>
static void * operator new(size_t size);<br>
<br>
...<br>
<br>
private:<br>
union {<br>
airplanerep *rep; // 用于被使用的对象<br>
airplane *next; // 用于没被使用的(在自由链表中)对象<br>
};<br>
<br>
// 类的常量,指定一个大的内存块中放多少个<br>
// airplane对象,在后面初始化<br>
static const int block_size;<br>
<br>
static airplane *headoffreelist;<br>
<br>
};<br>
<br>
上面的代码增加了的几个声明:一个operator new函数,一个联合(使得rep和next域占用同样的空间),一个常量(指定大内存块的大小),一个静态指针(跟踪自由链表的表头)。表头指针声明为静态成员很重要,因为整个类只有一个自由链表,而不是每个airplane对象都有。<br>
<br>
下面该写operator new函数了:<br>
<br>
void * airplane::operator new(size_t size)<br>
{<br>
// 把“错误”大小的请求转给::operator new()处理;<br>
// 详见条款8<br>
if (size != sizeof(airplane))<br>
return ::operator new(size);<br>
<br>
airplane *p = // p指向自由链表的表头<br>
headoffreelist; //<br>
<br>
// p 若合法,则将表头移动到它的下一个元素<br>
//<br>
if (p)<br>
headoffreelist = p->next;<br>
<br>
else {<br>
// 自由链表为空,则分配一个大的内存块,<br>
// 可以容纳block_size个airplane对象<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -