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

📄 8660.htm

📁 C++细节解释
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<HTML>
<HEAD>
<meta http-equiv='Content-Type' content='text/html; charset=gb2312'>


<style >
.fst{padding:0px 15px;width:770px;border-left:0px solid #000000;border-right:0px solid #000000}
.fstdiv3 img{border:0px;border-right:8px solid #eeeecc;border-top:6px solid #eeeecc}
</style>
<title>
Effective C++ 2e Item10
</title>
</HEAD>
<BODY >
<center>

<div align=center><div class=fst align=left><div class=fstdiv3 id=print2>
<p><b>
Effective C++ 2e Item10 
&nbsp;&nbsp;</b></p> 
<p>条款10. 如果写了operator new就要同时写operator delete</p> 
<p>让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?</p> 
<p>答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。</p> 
<p>例如有这样一个表示飞机的类:类airplane只包含一个指针,它指向的是飞机对象的实际描述(此技术在条款34进行说明):</p> 
<p>class airplanerep { ... };&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 表示一个飞机对象<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // <br>class airplane {<br>public:<br>&nbsp; ...<br>private:<br>&nbsp; airplanerep *rep;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 指向实际描述<br>};</p> 
<p>一个airplane对象并不大,它只包含一个指针(正如条款14和m24所说明的,如果airplane类声明了虚函数,会隐式包含第二个指针)。但当调用operator new来分配一个airplane对象时,得到的内存可能要比存储这个指针(或一对指针)所需要的要多。之所以会产生这种看起来很奇怪的行为,在于operator new和operator delete之间需要互相传递信息。</p> 
<p>因为缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。有一种常用的方法可以让operator new来告诉operator delete当初分配的内存大小是多少,就是在它所返回的内存里预先附带一些额外信息,用来指明被分配的内存块的大小。也就是说,当你写了下面的语句,</p> 
<p>airplane *pa = new airplane;</p> 
<p>你不会得到一块看起来象这样的内存块:</p> 
<p>&nbsp;&nbsp;&nbsp; pa——&gt; airplane对象的内存</p> 
<p>而是得到象这样的内存块:</p> 
<p>&nbsp;&nbsp;&nbsp; pa——&gt; 内存块大小数据 + airplane对象的内存</p> 
<p>对于象airplane这样很小的对象来说,这些额外的数据信息会使得动态分配对象时所需要的的内存的大小翻番(特别是类里没有虚拟函数的时候)。</p> 
<p>如果软件运行在一个内存很宝贵的环境中,就承受不起这种奢侈的内存分配方案了。为airplane类专门写一个operator new,就可以利用每个airplane的大小都相等的特点,不必在每个分配的内存块上加上附带信息了。</p> 
<p>具体来说,有这样一个方法来实现你的自定义的operator new:先让缺省operator new分配一些大块的原始内存,每块的大小都足以容纳很多个airplane对象。airplane对象的内存块就取自这些大的内存块。当前没被使用的内存块被组织成链表——称为自由链表——以备未来airplane使用。听起来好象每个对象都要承担一个next域的开销(用于支持链表),但不会:rep域的空间也被用来存储next指针(因为只是作为airplane对象来使用的内存块才需要rep指针;同样,只有没作为airplane对象使用的内存块才需要next指针),这可以用union来实现。</p> 
<p>具体实现时,就要修改airplane的定义,从而支持自定义的内存管理。可以这么做:</p> 
<p>class airplane {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 修改后的类 — 支持自定义的内存管理<br>public:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // </p> 
<p>&nbsp; static void * operator new(size_t size);</p> 
<p>&nbsp; ...</p> 
<p>private:<br>&nbsp; union {<br>&nbsp;&nbsp;&nbsp; airplanerep *rep;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 用于被使用的对象<br>&nbsp;&nbsp;&nbsp; airplane *next;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 用于没被使用的(在自由链表中)对象<br>&nbsp; };</p> 
<p>&nbsp; // 类的常量,指定一个大的内存块中放多少个<br>&nbsp; // airplane对象,在后面初始化<br>&nbsp; static const int block_size;</p> 
<p>&nbsp; static airplane *headoffreelist;</p> 
<p>};</p> 
<p>上面的代码增加了的几个声明:一个operator new函数,一个联合(使得rep和next域占用同样的空间),一个常量(指定大内存块的大小),一个静态指针(跟踪自由链表的表头)。表头指针声明为静态成员很重要,因为整个类只有一个自由链表,而不是每个airplane对象都有。</p> 
<p>下面该写operator new函数了:</p> 
<p>void * airplane::operator new(size_t size)<br>{<br>&nbsp; // 把“错误”大小的请求转给::operator new()处理;<br>&nbsp; // 详见条款8<br>&nbsp; if (size != sizeof(airplane))<br>&nbsp;&nbsp;&nbsp; return ::operator new(size);</p> 
<p>&nbsp; airplane *p =&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // p指向自由链表的表头 <br>&nbsp;&nbsp;&nbsp; headoffreelist;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // </p> 
<p>&nbsp; // p 若合法,则将表头移动到它的下一个元素<br>&nbsp; // <br>&nbsp; if (p)<br>&nbsp;&nbsp;&nbsp; headoffreelist = p-&gt;next;</p> 
<p>&nbsp; else {<br>&nbsp;&nbsp;&nbsp; // 自由链表为空,则分配一个大的内存块,<br>&nbsp;&nbsp;&nbsp; // 可以容纳block_size个airplane对象<br>&nbsp;&nbsp;&nbsp; airplane *newblock =<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static_cast&lt;airplane*&gt;(::operator new(block_size *<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(airplane)));</p> 
<p>&nbsp;&nbsp;&nbsp; // 将每个小内存块链接起来形成一个新的自由链表<br>&nbsp;&nbsp;&nbsp; // 跳过第0个元素,因为它要被返回给operator new的调用者<br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; for (int i = 1; i &lt; block_size-1; ++i)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newblock[i].next = &amp;newblock[i+1];</p> 
<p>&nbsp;&nbsp;&nbsp; // 用空指针结束链表<br>&nbsp;&nbsp;&nbsp; newblock[block_size-1].next = 0;</p> 
<p>&nbsp;&nbsp;&nbsp; // p 设为表的头部,headoffreelist指向的<br>&nbsp;&nbsp;&nbsp; // 内存块紧跟其后<br>&nbsp;&nbsp;&nbsp; p = newblock;<br>&nbsp;&nbsp;&nbsp; headoffreelist = &amp;newblock[1];<br>&nbsp; }</p> 
<p>&nbsp; return p;<br>}</p> 
<p>如果你读了条款8,就会知道在operator new不能满足内存分配请求时,会执行一系列与new-handler函数和例外有关的例行性动作。上面的代码没有这些步骤,这是因为operator new管理的内存都是从::operator new分配来的。这意味着只有::operator new失败时,operator new才会失败。而如果::operator new失败,它会去执行new-handler的动作(可能最后以抛出异常结束),所以不需要airplane的operator new也去处理。换句话说,其实new-handler的动作都还在,你只是没看见,它隐藏在::operator new里。</p> 
<p>有了operator new,下面要做的就是给出airplane的静态数据成员的定义:</p> 
<p>airplane *airplane::headoffreelist;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>const int airplane::block_size = 512;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>没必要显式地将headoffreelist设置为空指针,因为静态成员的初始值都被缺省设为0。block_size决定了要从::operator new获得多大的内存块。</p> 

⌨️ 快捷键说明

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