﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Euan's Note-随笔分类-C++读书笔记</title><link>http://www.cppblog.com/Euan/category/13267.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 15 Mar 2010 15:36:03 GMT</lastBuildDate><pubDate>Mon, 15 Mar 2010 15:36:03 GMT</pubDate><ttl>60</ttl><item><title>Effective C++  6.继承与面向对象设计</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109776.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:54:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109776.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109776.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109776.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109776.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109776.html</trackback:ping><description><![CDATA[<p>========================<br>Effective C++&nbsp;&nbsp; 继承与面向对象设计<br>书作者：Scott Meyers<br>原笔记作者：Justin <br>========================<br>&nbsp;<br>Item 32 ： public 继承意味着 is-a 关系<br>--------------------------------------------------<br>&nbsp;tag: public inheritance 公有继承&nbsp; is-a<br>&nbsp;<br>&nbsp;每一个类型为 Derived 的对象同时也是一个类型为 Base 的对象，反之不成立。<br>&nbsp;实际情况中很多&#8220;是一个&#8221;的体现并不那么纯粹：大师说&#8220;鸟&#8221;都会飞，但是实际上是有不会飞的&#8220;鸟&#8221;的。 </p>
<p>&nbsp;在公有继承中，有两种办法来解决这种&#8220;不纯粹&#8221;：<br>&nbsp; - 多重继承。对于&#8220;鸟&#8221;的例子，设计一个&#8220;鸟&#8221;类，然后从中派生出一个&#8220;不会飞的鸟&#8221;类和一个&#8220;会飞的鸟&#8221;类，然后再在它们之中分别派生其他具体的&#8220;鸟&#8221;们。<br>&nbsp; - 允许运行时出错。还是&#8220;鸟&#8221;的例子，对于每一个&#8220;鸟&#8221;类的派生类，不管它是不是能飞，都会有个&#8220;飞&#8221;的函数。不同的是，能飞的&#8220;鸟&#8221;就直接飞了，不能飞的&#8220;鸟&#8221;则会在&#8220;飞&#8221;函数里说：&#8221;对不起，我不能飞，找别人去吧&#8230;&#8230;&#8221;(所谓的运行时错误，runtime error) </p>
<p>&nbsp;<br>Item 33 ： 避免遮掩继承而来的名称<br>--------------------------------------------------<br>&nbsp;tag: scopes<br>&nbsp;&#183;derived classes 内的名称会遮掩 base classes 内的名称。<br>&nbsp;&#183;可以使用 Using 声明式或转角函数 (forwarding functions）。<br>&nbsp;<br>&nbsp;先在本地域中查找(local scope，比如说函数内部)是否有该名字的定义，如果没有找到<br>&nbsp;&nbsp; &nbsp;&nbsp; 往外一层名字域(比如说函数所在的类)中查找，如果没有找到&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再往外一层名字域(比如说函数所在类的父类)中查找，如果没有找到<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 继续忘外一层名字域中查找(比如说函数所在类的父类的父类，等等)，一直找到全局名字域(global scope)还是没找到的话，就报告错误。</p>
<p>&nbsp;在&#8220;洋葱&#8221;的内部某层定义了和外部某层一样名字的函数：使得位于内部的函数&#8220;屏蔽&#8221;了外部的同名函数(哪怕两个函数拥有不同的参数表)。<br>&nbsp;&nbsp;第一，在公有继承中，上述的情况是不允许存在的，因为从定义上来说，公有继承中的子类应该具备父类所有的特征和功能，应该&#8220;是一个&#8221;父类。<br>&nbsp;&nbsp;第二，如果在上述情况中需要调用/访问被&#8220;屏蔽&#8221;的函数/对象，有两个方法可以采用： <br>&nbsp;&nbsp;&nbsp;using。用using&#8220;声明&#8221;过完整的名字后，就可以&#8220;看见&#8221;并使用这个函数/对象了。 <br>&nbsp;&nbsp;&nbsp;踢皮球函数(forwarding functions)。编写一个函数，把真正的活踢给别人&#8230;&#8230; <br>&nbsp;两种方法示例见下，Derived_0是有&#8220;屏蔽&#8221;问题的类，Derived_1和Derived_2分别是采用了第一种和第二种方法的类。 </p>
<p>&nbsp; class&nbsp; Base&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1( int&nbsp; param);<br>&nbsp; } <br>&nbsp; <br>&nbsp; class&nbsp; Derived_0:&nbsp; public&nbsp; Base&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1();<br>&nbsp; } <br>&nbsp; <br>&nbsp; class&nbsp; Derived_1:&nbsp; public&nbsp; Base&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; using&nbsp; Base::func_1;<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1();<br>&nbsp; } <br>&nbsp; <br>&nbsp; class&nbsp; Derived_2:&nbsp; private&nbsp; Base&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func_1( int&nbsp; param)<br>&nbsp;&nbsp;&nbsp;&nbsp; { Base::func_1(param);} <br>&nbsp;&nbsp; } </p>
<p><br>Item 34 ： 区分接口继承和实现继承<br>--------------------------------------------------<br>&nbsp;tag: function interfaces,&nbsp;&nbsp; function implementations.<br>&nbsp;<br>&nbsp;&#183;声明一个 pure virtual 函数以让 derived classes 只继承函数接口<br>&nbsp;&#183;声明 普通virtual 函数以让 derived classes 继承该函数的接口和缺省实现<br>&nbsp;&#183;声明 non-virtual 函数以令 derived classes 继承函数的接口及一份强制性实现。<br>&nbsp;<br>&nbsp; class&nbsp; AClass&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_1()&nbsp; =&nbsp;&nbsp; 0 ;<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_2()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* the default implementation..*/ } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; interface_3()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* the compulsory implementation..*/ } <br>&nbsp; // .. <br>&nbsp; } ;</p>
<p>&nbsp; class&nbsp; AClassDerived&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_1()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* OK you have to implement this..*/ } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_2()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* you can, but don't have to implement this..*/ } <br>&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; void interface_3()<br>&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; {you can't implement your own..} <br>&nbsp; } ; </p>
<p>&nbsp; class&nbsp; AClass&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_1. 5 ()&nbsp; =&nbsp;&nbsp; 0 ;<br>&nbsp; protected :<br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; default_interface_1. 5 ()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* ..*/ } <br>&nbsp; } ;</p>
<p>&nbsp; class&nbsp; AClassDerived&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_1. 5 ()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // you can either do this <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default_interface_1. 5 ();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // or implement in your own way.. <br>&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp; } ; <br>&nbsp; <br>&nbsp; <br>Item 35 ： 考虑 virtual 函数意外的其他选择<br>--------------------------------------------------<br>&nbsp;tag: tr1::function&nbsp; tr1::bind (祥书上实例), <br>&nbsp;&nbsp; Template Method,&nbsp; Strategy , <br>&nbsp;&#183;使用 non-virtual interface(NVI)手法，即 Template Method 设计模式的一种特殊形式。以public non-virtual成员函数包裹较低访问性的virtual函数<br>&nbsp;&#183;将virtual函数替换为&#8220;函数指针成员变量&#8221;，即Strategy设计模式的一种分解表现形式<br>&nbsp;&#183;以tr1::function成员变量替换virtual函数，因而允许使用任何可调用物（callable entiry）搭配一个兼容于需求的签名式。也是Strategy设计模式的某种形式。<br>&nbsp;&#183;将继承体系内的 virtual函数替换为另一个继承体系内的 virtual函数。即Strategy设计模式的传统实现手法。<br>&nbsp;----------------------------------<br>&nbsp;一： Non-Virtual Interface 手法实现 Template Method 模式<br>&nbsp;<br>&nbsp;：让客户通过 public non-virtual 成员函数间接调用 private virtual 函数,此即NVI手法，Template Method 设计模式的一种表现形式。<br>&nbsp;&nbsp; 这个 public non-virtual 函数成为 virtual 函数的外覆器(wrapper)。<br>&nbsp;&nbsp; 优点在于可在 wrapper 中做一些事前工作和事后工作。 <br>&nbsp;&nbsp; NVI 手法中virtual function 不一定得是 private.<br>&nbsp;<br>&nbsp;----------------------------------<br>&nbsp;二： Function Pointers 实现 Strategy 模式<br>&nbsp;<br>&nbsp;：这种方法的实质，就是把接口函数的实现拿到了类之外。类之中只声明接口的形式，只定义一个函数指针。真正干活的函数（实现）都不是类的成员。<br>&nbsp;这样做带来了一定的灵活性，具体采用哪种实现与类的继承关系是独立无关联的；同时，非类成员函数也有局限性：无法访问类的非公有成员。如果把函数定义为友元或利用公有函数输出私有成员，又会破坏原设计的 封装。如下代码所示： </p>
<p>&nbsp; class&nbsp; AClass<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef&nbsp; void *(Interface)( /* param.. */ );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; explicit&nbsp; AClass( Interface pint&nbsp; =&nbsp; defaultInterface) : pInterface(pint)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {} <br>&nbsp;&nbsp;&nbsp;&nbsp; private :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Interface pInterface;<br>&nbsp; } ; <br>&nbsp;在构造AClass对象的时候即可指定Interface的真身，虽然，它无法直接访问AClass的非公有成员。<br>&nbsp;指针在C++里简单一些，更推崇用对象(如智能指针tr1)来管理接口函数。(是不是想到item13？:))</p>
<p>&nbsp;原理和函数指针是一样的，只不过因为用了对象来管理资源，使得应用更加灵活。当然，要付出更多一点的代码体积和运行时间代价。</p>
<p>&nbsp; class&nbsp; AClass<br>&nbsp; {<br>&nbsp; &nbsp;//&nbsp; all are the same with the funtion pointer version<br>&nbsp; &nbsp;//&nbsp; except for: <br>&nbsp;&nbsp;&nbsp;&nbsp; typedef std::tr1::function &lt; void ( /* param.. */ ) &gt;&nbsp; Interface;<br>&nbsp; } ; <br>&nbsp; <br>&nbsp; ---------------------------------<br>&nbsp; 三： 古典策略模式实现，也是我觉得比较漂亮且容易理解的实现方式。</p>
<p>&nbsp;用两个类搞定：<br>&nbsp;&nbsp;class AInterface<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void DoInterface(/* param.. */);<br>&nbsp;&nbsp;};</p>
<p>&nbsp;&nbsp;AInterface defaultInterface;</p>
<p>&nbsp;&nbsp;class AClass<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; explicit AClass(AInterface * pinter = &amp;defaultInterface) : pInter(pinter)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {} <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void TryInterface()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pInter-&gt;DoInterface();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pInterface * pInter;<br>&nbsp;&nbsp;};</p>
<p><br>&nbsp; <br>Item 36 ：绝不重新定义继承而来的non-virtual函数<br>--------------------------------------------------<br>&nbsp;tag: <br>&nbsp;<br>&nbsp;任何情况下都不该重新定义一个继承而来的non-virtual函数，</p>
<p>&nbsp; <br>Item 37 ：绝不重新定义继承而来的 缺省参数值<br>--------------------------------------------------<br>&nbsp;tag: 静态类型（static type）&nbsp; 动态绑定&nbsp; 静态绑定&nbsp; 前期绑定&nbsp; 后期绑定&nbsp; bound bind<br>&nbsp;<br>&nbsp;virtual 函数为动态绑定(dynamically bound),而缺省参数值却是静态绑定(statically bound)。<br>&nbsp;静态类型 ( static type )：<br>&nbsp;&nbsp;在程序中被声明时所采用的类型。<br>&nbsp;&nbsp;Shape* ps;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//静态类型为 Shape*, ps没有动态类型，因为为指向任何对象。<br>&nbsp;&nbsp;Shape* pc = new Circle;&nbsp;&nbsp;&nbsp;//静态类型为 Shape*， pc的动态类型为 Circle*<br>&nbsp;&nbsp;Shape* pr = new Rectangle;&nbsp;&nbsp;//静态类型为 Shape*, pr的动态类型为 Rectangle*<br>&nbsp;动态类型 ( dynamic type ) :<br>&nbsp;&nbsp;目前所指对象的类型，<br>&nbsp;&nbsp;virtual 函数系动态绑定而来，调用一个virtual函数时，调用哪一份实现代码，取决于发出调用的那个对象的动态类型。<br>&nbsp;&nbsp;<br>&nbsp; class&nbsp; AClass<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func(&nbsp; int&nbsp; param&nbsp; =&nbsp;&nbsp; 123&nbsp; )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // .. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp; } ;</p>
<p>&nbsp; class&nbsp; AClassDerived :&nbsp; public&nbsp; AClass<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; problematic overwriting the default parameter.. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; func(&nbsp; int&nbsp; param&nbsp; =&nbsp;&nbsp; 456&nbsp; )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // .. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp; } ;<br>&nbsp; int&nbsp; main()<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; AClass&nbsp; *&nbsp; pA&nbsp; =&nbsp;&nbsp; new&nbsp; AClassDerived;<br>&nbsp;&nbsp;&nbsp; pA -&gt; func();<br>&nbsp; } <br>&nbsp;由于函数默认参数的静态绑定特性，pA-&gt;func()执行时param事实上被赋予了123，而非子类中期望的456，虽然接下来执行的是子类的函数实现&#8230;&#8230;<br>&nbsp;C++考虑到执行效率和复杂性方面的代价，规定了只能是静态绑定的。<br>&nbsp;<br>&nbsp;解决方式：<br>&nbsp;&nbsp;可以用非虚函数接口(NVI)来解决这个问题，看代码<br>&nbsp;class AClass<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void func(int param = 123)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; funcImpl(param);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void funcImpl( int real_param ) = 0;<br>&nbsp;&nbsp;&nbsp; //..<br>&nbsp;};</p>
<p>&nbsp;class AClassDerived : public AClass<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp; private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void funcImpl( int real_param )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //do whatever you feel like to do here..<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; //..<br>&nbsp;};</p>
<p><br>&nbsp;<br>Item 38 ：通过 composition 塑模出 has-a 或 is-implemented-in-terms-of<br>--------------------------------------------------<br>&nbsp;tag: has-a&nbsp; composition&nbsp; is-implemented-in-terms-of 、根据list实现set(祥书上例）<br>&nbsp;<br>&nbsp;&#183;复合(composition)的意义同public继承完全不同。<br>&nbsp;&#183;在应用域(application domain),&nbsp;&nbsp; 复合意味着 has-a,<br>&nbsp;&nbsp; 在实现域(implementation domain),复合意味着 is-implemented-in-terms-of<br>&nbsp;<br>&nbsp;composition是类型之间的一种关系，即某种类型的对象内含它种类型的对象。<br>&nbsp;composition、 layering分层、&nbsp; containment内含、aggregation聚合、 embedding内嵌<br>&nbsp;composition意味着has-a 或 is-implemented-in-terms-of。</p>
<p>&nbsp;<br>Item 39 ：明智而审慎地使用 private 继承<br>--------------------------------------------------<br>&nbsp;tag: private inheritance<br>&nbsp;<br>&nbsp;Private inheritance意味着 implemented-in-terms-of.只有实现部分被继承，接口部分被略去。<br>&nbsp;若 D 以 private 形式继承 B, 意思是 D 对象根据 B 对象实现而得。<br>&nbsp;<br>&nbsp;公有继承中的子类对象是可以被转换为它的父类对象的(&#8220;是一个&#8221;的关系)，而私有继承中这种转换是不成立的。<br>&nbsp;另外一点，私有继承中父类的所有公有和保护成员(public和protected)到了子类中，都变成了私有成员。</p>
<p>&nbsp;因为上面的特性，私有继承并不满足&#8220;是一个&#8221;模型的需要。更可怜的是，私有继承并不能代表一种设计思路(公有继承代表了&#8220;是一个&#8221;的模型设计)，而仅仅是&#8220;有一个&#8221;模型的一种实现手段(私有继承过来的所有成员都是私有的，从这个角度来说它就只是&#8220;实现&#8221;)。<br>&nbsp;另一种手段大师在Item38中有提过，就是用类成员的方式来构造，名曰composition。<br>&nbsp;既然两者都能实现&#8220;有一个&#8221;模型，那么如何选择呢？能用composition就用composition，必需私有继承的时候方才私有继承。</p>
<p>&nbsp;比如我们有个AClass：class AClass{<br>&nbsp;public:<br>&nbsp;&nbsp;&nbsp; virtual void Interface_1(/*..*/);<br>&nbsp;};<br>&nbsp;以下为私有继承：class BClass : private AClass{<br>&nbsp;private:<br>&nbsp;&nbsp;&nbsp; virtual void Interface_1(/*..*/);<br>&nbsp;//..<br>&nbsp;};</p>
<p>&nbsp;而下面的composition可以达到一样甚至更好的效果：class AnotherAClass: public AClass{<br>&nbsp;public:<br>&nbsp;&nbsp;&nbsp; virtual void Interface_1(/*..*/);<br>&nbsp;//..<br>&nbsp;};</p>
<p>&nbsp;class DClass{<br>&nbsp;private:<br>&nbsp;&nbsp;&nbsp; AnotherAClass* a;<br>&nbsp;//..<br>&nbsp;};<br><br>&nbsp;BClass和DClass都实现了&#8220;有一个&#8221;，但相比之下还是能分辨出长短：</p>
<p>&nbsp;DClass中的AnotherAClass是私有成员，除了它自己没有人能够访问修改；而私有继承的BClass不能保证其&#8220;拥有&#8221;的AClass实现部分不会被第三者修改，即使是私有继承来的。(为什么这么说？看下去&#8230;&#8230;)<br>&nbsp;BClass私有继承了AClass，相当于它&#8220;有了一个&#8221;AClass可以用，可以玩。AClass中的公有/保护成员都变成了BClass的人，但是在享受使用这些成员的同时，BClass还要承担提供这些成员给别人服务的义务。<br>&nbsp;ITEM35中曾经提到：虚拟函数机制和公有/私有/保护体制是没有任何关系的。因此在例子中的Interface_1有可能在以下的情况中被替代然后&#8220;调用&#8221;： <br>&nbsp;一个CClass公有继承了BClass <br>&nbsp;CClass定义了自己的Interface_1版本 <br>&nbsp;有一个BClass的指针，指向一个CClass的对象，某个操作中调用了Interface_1(CClass的实现版本) </p>
<p>&nbsp;这时候BClass可能要有意见了：它的作者并没有打算让它的继承者修改BClass版本的Interface_1，但事实是CClass违背了它的意志！ <br>&nbsp;很曲折哈？希望我下次读的时候还能看懂@#￥% <br>&nbsp;DClass由于只是定义了一个指向AnotherAClass的指针，那么在定义DClass的文件中就不需要include AClass或AnotherAClass的头文件。于是就避免了编译依赖(compilation dependecies)<br>&nbsp;而BClass因为是继承了AClass，在BClass的文件中就需要加上AClass的头文件，也就不可避免的产生了编译时的依赖。 <br>&nbsp;由此看来，绝大部分情况下，组合方式(composition)是要优于私有继承的。之所以说&#8220;绝大部分&#8221;，是因为大师说了：<br>&nbsp;对于EBO(Empty Base Optimization)的情况，私有继承就显现出了它的优势。<br>&nbsp;所谓EBO就是这样的一种情况，有一种特殊的类，它没有非静态数据成员(non-static data member)，也没有虚函数(于是不会需要空间存储虚表)。<br>&nbsp;所以这样的一种类其实不占用任何空间，不过因为C++不允许0字节的对象存在，而且很多编译器都会添加额外的空间来实现字节对齐，于是这种特殊的类的实际大小应该是1个char对象的大小。<br>&nbsp;在这种类中，往往会有很多typedef，enum，静态数据成员或者是非虚函数。所以他们还是有价值的。<br>&nbsp;需要在&#8220;有一个&#8221;关系中利用这种类的时候，如果采用composition，那么根据上面的结论，就需要付出额外的空间来&#8220;存放&#8221;这个本来不占空间的类。<br>&nbsp;然而如果是私有继承呢，就可以避免这种情况。</p>
<p>&nbsp;<br>Item 40 ：明智而审慎地使用多重继承<br>--------------------------------------------------<br>&nbsp;tag: Multiple Inheritance, MI<br>&nbsp;<br>&nbsp;MI 的优与劣。 </p>
<p>&nbsp;MI 的第一个问题就是名字冲突， 最经典的例子就是钻石问题 (diamond problem)。<br>&nbsp;设想 A 中有一个函数叫做 GetName()， B 和 C 中都将有这一函数成员，这个时候 D::GetName() 的真正实现是来自 B 的还是 C 的呢？二义性出现了 (ambiguity) 。 </p>
<p>&nbsp;不过如果真的发生了这种情况，要解决的方法也不是没有，可以这样做：</p>
<p>&nbsp;D d；<br>&nbsp;d.B::GetName(); //Calling B's implementation</p>
<p>&nbsp;另外一个高阶一点的方法叫做虚继承 (virtual inheritance) 。对于在虚拟继承中的父类，其中的成员都保证不会在后面的子类中出现二义现象 (ambiguity) 。</p>
<p>&nbsp;class A<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void GetName();<br>&nbsp;};<br>&nbsp;class B : virtual public A&nbsp;{&nbsp;};<br>&nbsp;class C : virtual public A&nbsp;{&nbsp;};<br>&nbsp;class D : public B, public C&nbsp;{&nbsp;}<br>&nbsp;D d;<br>&nbsp;d.GetName(); //there is no ambiguity here.</p>
<p>&nbsp;但是虚继承不是没有代价的，大师说这种技术会使得最终代码变得更大，访问虚拟继承中的父类成员也会变得更慢一些。 </p>
<p>&nbsp;这个也不难理解。和空间换时间一样，和不给牛吃草牛就不干活一样。 ( 另外的一个代价我还没能完全理解透彻：书上说因为虚继承中基类的初始化是由继承关系中最底层的子类负责的，因此对于这些最底下的 &#8220; 嫡孙 &#8221; 类来说，就不是那么方便了 ) </p>
<p>&nbsp;于是大师建议只有在必要的时候才使用虚继承，而在虚继承中的基类里也不要放置数据成员，这样就不用担心初始化的问题了。 </p>
<p>&nbsp;不过存在就是合理，还是有需要用到 MI 的时候。一个在书中提到的使用 MI 的情形是：当需要从一个类 AClass 中继承接口，又需要从另外一个类 BClass 中继承实现细节时，就可以考虑在公有继承 AClass 的同时又私有继承 BClass 。道理大致就是这样，就不编造程序画蛇添足了。 </p>
<p>&nbsp;总结一下： MI 比 SI(Single Inheritance) 要复杂容易出错 ( 比如说钻石问题 ) ，即使可以用虚继承来解决钻石问题，但是其带来的代码体积增大，访问效率下降以及初始化问题还是不能忽视的。最后话说回来，需要用到 MI 的时候，小心点用便是 </p>
<img src ="http://www.cppblog.com/Euan/aggbug/109776.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:54 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109776.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective C++  5. 实现</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109775.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:53:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109775.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109775.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109775.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109775.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109775.html</trackback:ping><description><![CDATA[<p>========================<br>Effective C++&nbsp;&nbsp; 实现<br>书作者：Scott Meyers<br>原笔记作者：Justin <br>========================</p>
<p>Item 26 ： 尽可能延后变量定义式的出现时间<br>--------------------------------------------------<br>&nbsp;tag:<br>&nbsp;&#183;尽可能延后变量定义式的出现，以增加程序的清晰度并改善程序效率。<br>&nbsp;<br>&nbsp;定义变量包含了该变量对象的构造操作，如果因为某个原因(如抛出异常，条件语句未执行等)而没有真正用到这个变量，那么构造该变量所耗费的时间和资源就白费了。 <br>&nbsp;在即将使用变量前再定义它对理解代码也有好处：要想知道某个变量时做什么用的？读接下来的代码便是。 </p>
<p>&nbsp;思考题，以及答案：<br>&nbsp;//方法A：循环外定义<br>&nbsp;Widget w;<br>&nbsp;for (int i = 0; i &lt; n; ++i){<br>&nbsp;&nbsp;&nbsp; w = some_value_dependent_on_i;&nbsp;&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; <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; </p>
<p>&nbsp;//方法B:循环内定义<br>&nbsp;for (int i = 0; i &lt; n; ++i) {<br>&nbsp;Widget w(some_value_dependent_on_i);<br>&nbsp;//..<br>&nbsp;}<br>&nbsp;&nbsp;方法A调用了1次构造函数、1次析构函数、n次拷贝函数； <br>&nbsp;&nbsp;方法B调用了n次析构函数、n次析构函数。 <br>&nbsp;&nbsp;<br>&nbsp;&nbsp;当 拷贝操作的开销 比 构造-析构操作 要廉价的时候，一般来说A方法是上选。 <br>&nbsp;&nbsp;但是A方法中对象的作用域比B方法中更大，也就违背了代码的集中性和可维护性原则。 <br>&nbsp;&nbsp;因此，除非 <br>&nbsp;&nbsp;&nbsp;&nbsp; 拷贝操作比构造-析构操作开销小，并且此部分代码对性能(performance)要求很高，(此时选择为A) <br>&nbsp;&nbsp;否则B方法还是更合理。 </p>
<p><br>Item 27 ： 少做转型动作<br>--------------------------------------------------<br>&nbsp;tag: cast 转型&nbsp;&nbsp;&nbsp; const_cast&nbsp;&nbsp;&nbsp; dynamic_cast&nbsp;&nbsp;&nbsp; reinterpret_cast&nbsp;&nbsp;&nbsp; static_cast<br>&nbsp;<br>&nbsp;&#183;尽量避免转型，特别是注重效率的代码中避免 dynamic_casts.尽量将要转型的设计转化为无需转型。<br>&nbsp;&#183;若转型必须，试着将它隐藏于某个函数背后。客户随后可以随时调用该函数，而不需将转型放进他们自己的代码中。<br>&nbsp;&#183;另可使用 C++ style转型，也不要使用旧式转型。前者更容易分辨。<br>&nbsp;<br>&nbsp;类型转换的三种形式(前面两种都是C风格的旧式类型转换)：<br>&nbsp;&nbsp;(T)expression<br>&nbsp;&nbsp;T(expression) <br>&nbsp;&nbsp;C++ Style:<br>&nbsp;&nbsp;&nbsp;const_cast：设置或是去除对象的const属性。 <br>&nbsp;&nbsp;&nbsp;dynamic_cast：主要用于继承关系层次中的向上、向下转换，以及类之间的交叉转换。会进行转换安全性检查。 <br>&nbsp;&nbsp;&nbsp;static_cast：可用于内置类型的转换，以及继承关系层次中的向上转换。没有转换安全性检查。 <br>&nbsp;&nbsp;&nbsp;reinterpret_cast：简单的强制将一个指针转换为另外一种指针或整数类型，不做任何检查。 <br>&nbsp;<br>&nbsp;类型转换还可能引发额外的代码运行。比如说dynamic_cast就会通过调用strcmp来比较类的名称，从而完成继承关系中不同类对象的转换，这个时候就不仅仅是简单的变变类型了。因此，说&#8220;类型转换仅是告诉编译器把一种类型的数据当成另外一种来参与计算&#8221;其实是一个理解上的误区。<br>&nbsp;类型转换也有可能带来额外开销：比如书中用static_cast进行的继承关系的向上转换，就会自作主张地生成一个临时的对象。</p>
<p>&nbsp;dynamic_cast 通常是在一个认定为 derived class 对象上执行 derived class操作函数，但手上只有一个&#8220;指向base&#8221;的pointer或reference。<br>&nbsp;&nbsp;可以用以下两种方法来避免这个问题：<br>&nbsp;&nbsp;1. 使用容器并在其中存储直接指向 derived class 对象的指针（通常为智能指针），以消除通过base class接口处理对象的需要。<br>&nbsp;&nbsp;2. 在base class内提供virtual函数做你想对各个derived classes做的事。<br>&nbsp;要避免所谓的&#8220;连串（cascading）dynamic_casts&#8221;<br>&nbsp;<br>&nbsp;在C++中，两个指向同一个对象的不同指针可能拥有不同的地址值。<br>&nbsp;因此，不仅要尽可能的避免转换类型，而且在不得不使用类型转换的时候，也应该考虑将转换的代码用函数封装起来。 </p>
<p><br><br>Item 28 ： 避免返回 handles 指向对象内部成分<br>--------------------------------------------------<br>&nbsp;tag: 返回值<br>&nbsp;<br>&nbsp;避免返回 handles（包括reference、pointer、iterator）指向对象内部。<br>&nbsp;增加封装性，帮助const成员函数的行为像个const，并将发生&#8220;悬垂handles&#8221;的可能性降至最低。<br>&nbsp;</p>
<p>&nbsp;如果只需要读访问，就使用const的返回值，不要开放写的权限。<br>&nbsp;有可能产生悬垂指针(dangling pointer)也是暴露对象内部成员handles的后果之一。<br>&nbsp;<br>&nbsp;一个返回对象内部成员的函数，在用户不正确使用的情况下，就有可能产生悬垂指针。<br>&nbsp;&nbsp;class AClass{//..};<br>&nbsp;&nbsp;class BClass{<br>&nbsp;&nbsp;//..<br>&nbsp;&nbsp;const AClass&amp; FuncReturningARef();<br>&nbsp;&nbsp;//..<br>&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;//a possible user's code<br>&nbsp;&nbsp;BClass AnObjectOfB;<br>&nbsp;&nbsp;const AClass *pAClass = &amp;(AnObjectOfB.FunReturningARef());<br>&nbsp;&nbsp;//After the call pAClass becomes a dangeling pointer..<br>&nbsp;<br></p>
<p>Item 29 ： 编写对异常免疫(exception-safe)的代码<br>--------------------------------------------------<br>&nbsp;tag: Exception safety<br>&nbsp;<br>&nbsp;&#183;Exception safety functions即使发生异常也不回泄露字元或允许任何数据结构败坏。区分为三种可能的保证：基本型、强类型、不抛出异常型<br>&nbsp;&#183;强烈保证型通常能以 copy-and-swap 实现，但强烈保证兵匪对所有函数都可实现或具备现实意义。<br>&nbsp;&#183;函数提供的&#8220;Exception-safe保证&#8221;通常只等于其所调用之各个函数的&#8220;Exception-safe保证"中的最弱者。</p>
<p>&nbsp;对异常免疫的函数在异常发生的时候应该具备两个特征：<br>&nbsp;&nbsp;不泄漏任何资源(内存、锁等等) <br>&nbsp;&nbsp;不造成任何数据结构的损坏 <br>&nbsp;并能够提供至少以下保证中的一项：</p>
<p>&nbsp;Exception-safe functions 提供以下三个保证之一：<br>&nbsp;&nbsp;基本的保证：当异常抛出时，程序中的对象、数据免遭破坏。 <br>&nbsp;&nbsp;较强的保证：当异常抛出时，程序的状态不会被改变。若成功调用函数，则系统进入成功后的状态；如果函数中因异常而出错，系统应该留在调用函数前的状态：<br>&nbsp;&nbsp;最强的保证：不会有异常抛出。例如对内置类型的操作就不会抛出异常。这是最理想的，但也很难做到。更多的函数只能在前两者中做一选择。</p>
<p>&nbsp;为了能够提供较强的保证，也即系统的状态不因异常抛出与否而变化，大师又重新提出了&#8220;先拷贝后交换&#8221;(copy-and-swap)这一方法论来。<br>&nbsp;用不那么严谨的说法：为了避免在操作对象时触发异常影响系统状态，&#8220;先拷贝后交换&#8221;先是创建了一个临时对象，将所有的操作都施加在该临时对象上。如果没有出错，把这个处理过的临时对象和真正需要处理的对象交换一通，算是顺利完成任务；如果有错并抛出了异常，原系统状态也不会被影响，因为真正需要处理的对象根本没有被动过。<br>&nbsp;当然，天下没有免费的午餐。<br>&nbsp;&#8220;先拷贝后交换&#8221;不仅耗费了一个临时对象的存储代价，同时支出的还有后面交换对象时的时间和资源开销。因此，对异常免疫的较强保证是很好很强大，但是实际中并不是任何时候都需要做到那么高的保证。杀鸡岂需用牛刀？</p>
<p>&nbsp;最后要提醒的是，对异常免疫的函数也符合&#8220;短板理论&#8221;：木桶能装的水与其最短的那块木板有关，函数对异常免疫的程度也由函数中程度最低的代码(包括其调用的函数)决定。某个函数如果调用了另外一个一出现异常就崩溃的函数，那么这个函数就不能提供基本的异常免疫保证。</p>
<p><br>Item 30 ： 透彻了解 inlining<br>--------------------------------------------------<br>&nbsp;tag: inline<br>&nbsp;&#183;将大多数 inlining 限制在小型、被频繁调用的函数身上。可使日后的调用过程和二进制升级更容易，也使程序的速度提升机会更大。<br>&nbsp;&#183;不要只因为 function templates 出现在头文件，就将他们声明为 inline.<br>&nbsp;<br>&nbsp;使用内联函数(inline function)可以省去一般函数调用的入栈操作开销，比宏(macro)要好用。从编译器的角度来看，没有函数调用的代码要更容易优化。<br>&nbsp;但是天下没有免费的午餐，以空间换时间的内联函数同时也带来了更大的程序占用空间，更甚者还会因为这变大的代码空间导致额外的内存换页操作，降低指令缓存(instruction cache)的命中率&#8230;&#8230;这些都是使用内联函数需要考虑到的负面影响。(Scott还是辩证地提醒了一点：当内联函数非常短小时，相比一般意义上的函数调用，它能够帮助编译器生成更小的最终代码和运行时更高的指令缓存命中率)</p>
<p>&nbsp;内联函数的声明可以是显式的：使用inline关键字；也可以是隐式的：在类的定义中定义函数。</p>
<p>&nbsp;内联函数一般而言都是定义在头文件中，这是因为大多数编译器的内联动作都是发生在编译过程中。(也有着链接甚至是运行中才进行内联的，但是俺们这里随大流，讲主要矛盾)<br>&nbsp;虽然内联函数和函数模板有一点相似：它们都几乎定义在头文件中，但是这两者之间没有必然联系，而非有的程序员想的那样&#8220;函数模板一定是内联的&#8221;)</p>
<p>&nbsp;内联函数的定义仅仅是对编译器提出内联的请求。编译器完全有可能忽视这个请求，于是某&#8220;内联函数&#8221;有可能在最后还是生成了一般函数的代码：</p>
<p>&nbsp;&nbsp;请求内联的函数有可能太过于复杂 <br>&nbsp;&nbsp;请求内联的函数有可能是虚函数(虚函数的真正实体要在运行时才能得知，让编译器编译阶段去做内联实在有点强人所难) <br>&nbsp;&nbsp;请求内联的函数没什么问题，但是在代码中有用函数指针的方式调用该函数(这样编译器也没办法，如果不生成一般函数哪来的函数指针？) <br>&nbsp;&nbsp;这些情况下确实不应该把函数作为内联函数。一个&#8220;内联函数&#8221;是否最终生成了内联函数，还得编译器说了算。<br>&nbsp;然而编译器并不是总能帮助我们做出正确的决定，还有一些情况是需要我们自己做出判断的：</p>
<p>&nbsp;请求内联的函数是构造/析构函数(表面上看起来某个构造/析构函数很短小甚至是空的，但是为了构造/析构类中的其他成员，编译器有可能会&#8220;自觉&#8221;地写入必要的代码，这样的构造/析构函数就有可能不适合再做内联了)这一点原文中有更详细的说明。 <br>&nbsp;当编写支持库时(library)也不建议使用内联函数，因为一旦用户使用了这些含有内联函数的库并编译了自己的程序，这些内联函数就已经&#8220;写死&#8221;在他们的程序中了。当日后对原先的库做了更新修改，用户就必须重新编译整个程序才能用上新的补丁。而一般的函数就不会有这个问题：他们是动态链接的，用户根本感觉不到任何改动。 <br>&nbsp;考虑到很多调试器(debugger)无法调试内联函数(本来就没有这么一个&#8220;函数&#8221;，叫人家怎么设断点？)，在调试版本中也不建议使用内联函数。 <br>&nbsp;有那么多需要注意的地方，大师最后总结了一下：用好内联函数的第一步就是：不用内联函数。并没有那么多的函数真正需要内联，因为80%的程序运行时间都是花在了20%的代码中。第二步是把内联函数当成是手工优化的手段，仅仅在非常需要效率和优化的代码中使用内联。 </p>
<p><br><br>Item 31 ： 将文件间的编译依存关系降至最低<br>--------------------------------------------------<br>&nbsp;tag: Handle class，Interface classes ,&nbsp; 接口类 实现类，<br>&nbsp;&#183;相依于声明式，不要相依于定义式。两个手段来实现：Handle classes 和 Interface classes.<br>&nbsp;&#183;程序库头文件应该以 &#8220; 完全且仅有声明式 &#8221;(full and declaration-only forms)的形式存在，不论是否涉及 templates.<br>&nbsp;<br>&nbsp;大师说了，C++的设计还是有缺陷的：它无法把接口(interface)的设计和实现(implementation)的设计完全划分开来。<br>&nbsp;比如说在一个类的(接口)声明当中，总是或多或少的会泄漏一些实现上的细节，虽然这样做与接口的设计并没有太多联系。</p>
<p>&nbsp; class&nbsp; AClass&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; interface_1();<br>&nbsp;&nbsp;&nbsp;&nbsp; std::string&nbsp; interface_2();<br>&nbsp; private :<br>&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; implementation details are leaking as below.. <br>&nbsp;&nbsp;&nbsp;&nbsp; std::string&nbsp; internalData_1;<br>&nbsp;&nbsp;&nbsp;&nbsp; BClass internalData_2;<br>&nbsp; }&nbsp;&nbsp;&nbsp;<br><br>&nbsp;往往还需要引用其他头文件中相关对象的定义(如下面的代码)，从而产生了对这些头文件的(在编译时的)依赖。因此每次这些文件中的某个有变化时，依赖它的所有文件都需要重新编译。<br>&nbsp; #include&nbsp; &lt; string &gt; <br>&nbsp;#include&nbsp; " BClass.h "&nbsp; //<br>&nbsp;<br>&nbsp;【注意】这里貌似逻辑不是很顺：就算没有那些私有成员的声明，接口函数的返回值如果是string或是BClass等类型，不还是一样需要依赖引用其他头文件吗？<br>&nbsp;这是两种不一样的情况，实现和接口。<br>&nbsp;&nbsp;前面说的实现细节的泄漏是会导致编译依赖的，因为编译器需要了解这些类型对象的大小进而为其分配内存空间；<br>&nbsp;&nbsp;但是接口，比如说函数的返回值或是参数表中的参数，就不需要编译器去考虑分配内存的问题，因此也就没有所谓的编译依赖了。</p>
<p><br>&nbsp;将类分割为两个classes，一个只提供接口，另一个负责实现该接口。<br>&nbsp;&nbsp; class&nbsp; AClassImpl&nbsp; <br>&nbsp;&nbsp; {<br>&nbsp;&nbsp; private :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; implementation details are moved here.. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::string&nbsp; internalData_1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BClass internalData_2;<br>&nbsp;&nbsp; } <br>&nbsp;&nbsp; <br>&nbsp;&nbsp; class&nbsp; AClass&nbsp; <br>&nbsp;&nbsp; {<br>&nbsp;&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; interface_1();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::string&nbsp; interface_2();<br>&nbsp;&nbsp; private :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; there is only a pointer to implementation <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::tr1::shared_ptr &lt; AClassImpl &gt;&nbsp; pImpl;<br>&nbsp;&nbsp;} <br>&nbsp;&nbsp; <br>&nbsp;&nbsp; // a constructor: instantiations of AClass and AClassImpl should always be bound together. <br>&nbsp;&nbsp; AClass::AClass( // ..) : pImpl(new AClassImpl( // ..)) <br>&nbsp;&nbsp; {&nbsp;&nbsp; }&nbsp;<br><br>&nbsp;分离的关键在于以&#8220;声明的依存性&#8221;替换&#8220;定义的依存性&#8221;：让头文件尽可能自我满足，如果不行，就让它与其他文件内的声明式（而非定义式）相依：<br>&nbsp;&#183;若使用 object references 或 object pointers 可以完成任务，就不要使用 objects.但如果要定义某类型的object，就需要用到定义式，。<br>&nbsp;&#183;尽量以class声明式替换class定义式。<br>&nbsp;&#183;为声明式和定义式提供不同的头文件。</p>
<p>&nbsp;第二种方法中，抽象类/接口类提供了所有接口的纯虚函数形式：会有该类的子类去实现这些接口。<br>&nbsp;在抽象类/接口类中还会有一个静态(static)的工厂函数(比如create()/produce()/factory()&#8230;&#8230;），这个函数实际上起到了构造函数的作用，它&#8220;制造&#8221;出子类对象来完成真正的任务，同时返回这个对象的指针(通常是智能指针如shared_ptr)。凭借这个返回的指针就可以进行正常的操作，同时不会有编译依赖的担心。一个简陋的代码见下： </p>
<p>&nbsp; class&nbsp; AClass: public&nbsp; AClassFactory&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AClass()&nbsp;&nbsp; {} <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; interface_1();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std:: string&nbsp; interface_2();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; ~ AClass();<br>&nbsp; } <br>&nbsp; <br>&nbsp; class&nbsp; AClassFactory&nbsp; {<br>&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; void&nbsp; interface_1()&nbsp; =&nbsp;&nbsp; 0 ;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp; std::string&nbsp; interface_2()&nbsp; =&nbsp;&nbsp; 0 ;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual&nbsp;&nbsp; ~AClassFactory()&nbsp; { /* .. */ } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static&nbsp; std::tr1::shared_ptr &lt; AClassFactory &gt;&nbsp; Produce( /* .. */ )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // this factory function could be more complicated in practice.. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp; std::tr1::shared_ptr &lt; AClassFactory &gt; ( new&nbsp; AClass);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp; } <br>&nbsp; <br>&nbsp; <br>&nbsp; // AClassFactory could be used in this way.. <br>&nbsp; std::tr1::shared_ptr &lt; AClassFactory &gt;&nbsp; pAClassObject;<br>&nbsp; pAClassObject&nbsp; =&nbsp; AClassFactory::Produce( /* .. */ );<br>&nbsp; // pAClassObject-&gt;.. </p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/Euan/aggbug/109775.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:53 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109775.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective C++   4. 设计与声明</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109774.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:50:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109774.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109774.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109774.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109774.html</trackback:ping><description><![CDATA[<p>========================<br>Effective C++&nbsp;&nbsp;&nbsp;设计与声明<br>书作者：Scott Meyers<br>原笔记作者：Justin <br>========================</p>
<p>Item 18 ： 接口应该容易被正确使用，不易被误用<br>--------------------------------------------------<br>&nbsp;tag：消除客户的资源管理责任&nbsp; tr1::shared_ptr&nbsp; cross-DLL problem<br>&nbsp;<br>&nbsp;用户使用接口时却没有获得预期的行为，这个代码不应该通过编译。<br>&nbsp;<br>&nbsp;用错的可能有：<br>&nbsp;&#183;调用接口时输入了错误的参数。<br>&nbsp;&nbsp;如(一个接受年、月、日为参数的接口函数，用户可以轻易给出各种错误的输入)，<br>&nbsp;&nbsp;解决办法：用对象来约束参数输入的范围(不接受简单的整数作为输入，而是Date、Mon、Year对象) <br>&nbsp;&nbsp;&nbsp;struct Day{ explicit Day(int d):val(d){}&nbsp; int val;}&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;class Month{<br>&nbsp;&nbsp;&nbsp;&nbsp;public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static Month May(){ return Month(5); }<br>&nbsp;&nbsp;&nbsp;&nbsp;private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;explicit Month(int m);<br>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;Date d(Month::Mar(),Day(20),Year(1995));<br>&nbsp;&nbsp;<br>&nbsp;&#183;用常规的用法调用&#8220;特别&#8221;设计的接口。所以需要尽可能的把自己的设计往常规上靠：数据对象的行为要尽可能符合内建对象(比如int)的行为；接口的名字和意义要尽可能一致(比如STL中的容器基本都有一个叫做size的返回容器大小的接口)&#8230;&#8230;这样做鼓励用户去正确的看待和使用你的接口。 <br>&nbsp;<br>&nbsp;&#183;忘了处理调用接口后的遗留问题。因此不要让用户去&#8220;记得&#8221;做一些事情。<br>&nbsp;&nbsp;如设计一个接口返回一个指向某新建对象的指针，该接口的用户需要&#8220;记得&#8221;去释放这个指针所指的对象：如果用户忘了释放或释放了好几次，后果就是@#￥%<br>&nbsp;&nbsp;解决的办法之一是让该接口返回一个智能指针(嗯&#8230;&#8230;印象模糊了？去看Item14)，这样用户用完了就可以&#8220;忘记&#8221;这个指针：它自己会处理后事。 <br>&nbsp;&nbsp;<br>&nbsp;&#183;所谓的&#8220;跨DLL问题&#8221;(cross DLL problem)：在一个DLL中new一个对象，然后对象被传到另外一个DLL里被delete。大师推荐用shared_ptr因为它解决了这个问题。 <br>&nbsp;&nbsp;代价：额外对象的创建和销毁需要时间空间。比如boost的shared_ptr就是普通指针的两倍大小，还有额外的对象操作时间+过程动态内存分配等。<br>&nbsp;&nbsp;实际上有些底层代码根本没这个资本提供这样的&#8220;豪华装备&#8221;，不过有这样的思想还是很重要D&#8230;&#8230;<br></p>
<p>Item 19 ：设计class犹如设计type<br>----------------------------------------<br>&nbsp;tag：class design <br>&nbsp;<br>&nbsp;&#183;小心设计类的创建和销毁方式。比如说Item8和Item16 <br>&nbsp;&#183;认真考虑如何区分类的构造函数和赋值（assignment）操作符。即初始化与赋值的差别。<br>&nbsp;&#183;注意实现类的传值(passed by value)。这个实际上是在说要注意拷贝构造函数的实现。 <br>&nbsp;&#183;切勿忽略类对非法输入的处理。其实是要注意各种出错情况，是否需要抛出异常以及如何实现异常处理。 <br>&nbsp;&#183;需要审视类所在的继承体系。<br>&nbsp;&nbsp;如果该类有父类，那么必定要受到父类的一些限制，特别是函数是否为虚构；如果该类有子类，那么就要考虑是不是一些函数需要定义为虚函数，比如说析构函数。 <br>&nbsp;&#183;谨慎实现类对象与其他类型对象的转换。这一点稍有些复杂：如果有将T1转换为T2的需求，就有隐式转换和显式转换两种方式。<br>&nbsp;&nbsp;对于前者，可以编写一个(隐式的)转换函数(参考Item15里面的隐式转换咯～)，或者是通过额外编写一个T2的构造函数来实现T1向T2的转换。<br>&nbsp;&nbsp;对于后者，Scott说写一个(显式的)转换函数就可以了。(同样，在Item15里也有显式转换函数的例子) <br>&nbsp;&#183;需要考虑该类需要参与哪些运算。很明显，如果需要参与A运算就要相应定义类的A运算符函数。大师在这里提的另外一点是，这些运算符号函数有些应该是成员函数，有些不应该。原因在Item23、24、26【555我还没看到，留空】 <br>&nbsp;&#183;不要提供不应该暴露的标准函数。这里的标准函数指的是构造/析构/拷贝等等可能由编译器&#8220;自愿&#8221;为你生成的函数，如果不希望它们中的一些被外界调用，就声明为私有(private)。没印象了？降级到Item6重新学习～ <br>&nbsp;&#183;注意设计类成员的访问权限。公有(public)、保护(protected)、私有(private)应该用哪一种？有没有需要定义友元？或者是干脆来一个类中类？都需要考虑。<br>&nbsp;&#183;认真审查类的隐性限制。性能上的要求、使用资源的限制或是出错时的处理都有可能影响到类的具体设计和实现。 <br>&nbsp;&#183;谨慎考虑类的适用范围。也就是说如果某个设计会可能用在很多方面，适用于许多不同的实际对象。也许这个时候你需要设计的不是一个类，而是一个类模板。 <br>&nbsp;&nbsp;最后一点其实应该放在第一位：你真的需要定义一个类吗？如果仅仅是在继承某类的基础上加一两个成员，是不是非成员函数或模板就已经够了捏？ </p>
<p>&nbsp;第19招其实更像是个check list，在准备动手设计之前，一一比对打勾划叉，应该可以提前避免很多人间惨剧&#8230;&#8230;<br><br></p>
<p>Item 20 ： 用 传const引用 替换&nbsp; 传值<br>-------------------------------------------<br>&nbsp;tag： const引用&nbsp; 值传递<br>&nbsp;<br>&nbsp;&nbsp;C++传递对象的时候默认是传值的(pass-by-value)，而这样的传递自然是昂贵的：这当中包含了临时对象的构造/析构，以及临时对象中的对象的构造/析构，运气背点还可能有对象中的对象中的对象的构造/析构&#8230;&#8230;(有好的不学，去学C@#￥%) </p>
<p>&nbsp;&#183;相对于传&#8220;值&#8221;，一个更好的替代方法是传&#8220;const引用&#8221;(pass-by-reference-to-const)。<br>&nbsp;&#183;传值与传指针的一个区别是，通过传值传递的对象并不是原来的对象，而是一个复制品，所以随便你打它骂它，真身都不会受到影响。<br>&nbsp;&#183;而通过传指针的对象和原来的对象就是同一家伙，改动一个另外一个也受到相同的影响。而这有时候并不是我们想要的结果。<br>&nbsp;&#183;考虑到传值代价太高，传&#8220;const引用&#8221;就成了一个很好的替代品。 </p>
<p>&nbsp;&#183;传&#8220;const引用&#8221;的另外一个好处在于避免了&#8220;剥皮问题&#8221;(slicing problem，侯捷大师的版本是&#8220;对象切割问题&#8221;，我用这个中文名字是为了更容易记住:))<br>&nbsp;&nbsp;用传值方式传参的函数，如果某参数的类型是一个父类对象，而实际传递的参数是一个子类对象，只有该对象的父类部分会被构造并传递到函数中，子类部分的成员，作为父类对象的&#8220;皮&#8221;，就被血淋淋的剥掉了&#8230;&#8230;<br>&nbsp;&nbsp;而如果用传&#8220;const引用&#8221;方式，就没有这种惨无人道的状况：本来父类的指针就可以用来指向一个子类对象，天经地义。 </p>
<p>&nbsp;例外：对于内置类型(bulit-in type)对象以及STL中的迭代器、函数对象，Scott还是建议使用传值方式传递，原因是他们本来就是被设计成适合传值传递的。<br>&nbsp;(个人观点：大师说：&#8220;&#8230;&#8230;it's not unreasonable to choose pass-by-value。&#8221;，注意这里有句潜台词：其实对以上类型用传&#8220;const引用&#8221;方式传递也是可以的。)</p>
<p>&nbsp;如果你认为上面两种情况可以用传值传递是因为它们，比如说内置类型对象，的大小本来就小，进而得出小数据类型就可以用传值传递，就打错特错了。小对象的构造/析构过程完全可能很恐怖。<br>&nbsp;再退一步，哪怕某个类型很小，它的构造/析构函数也简单到可以忽略不计，我们还是不能以此断定可以用传值传递这种类型的对象：因为编译器往往会做出一些蠢事。书中的一个例子是，对于一些编译器可以接受把一个double类型对象存入寄存器，但是如果你给它一个只有一个double成员的对象交给它，它却拒绝将该对象存入寄存器。(什么事让编译器插一手，不是问题也有了问题&#8230;&#8230;)<br>&nbsp;最后还有个理由，虽然某对象现在很小，可是随着社会的发展人类的进步，有可能两年后它就会变成一个庞然大物，到时候用传值也会变得不合适。 </p>
<p>&nbsp;&#183;除了内置类型和STL的迭代器、函数对象外，其他的对象传递时，用传&#8220;const引用&#8221;代替传值吧。 </p>
<p><br><br>Item 21：&nbsp; 该换回对象时别返回它的reference<br>---------------------------------------------------<br>&nbsp;tag：&nbsp; </p>
<p>&nbsp;如果一个函数可能返回一个对原来不存在的对象的引用，那么函数就要自己去创建这个对象：要么在栈上(stack)要么在堆上(heap)。</p>
<p>&nbsp;第一种情况中，函数中定义了局部对象，然后返回对该对象的引用。对象在函数结束后自动销毁，引用指向无效的地址。</p>
<p>&nbsp;第二种情况，函数使用new动态创建了一个对象，然后返回对该对象的引用。粗看没有问题，因为这个返回的引用还是有效的。<br>&nbsp;&nbsp;但是细想就会发现：我们能确保这个对象被正确的收回(delete)吗？ <br>&nbsp;&nbsp;书中举了一个很好的例子：一个*运算符函数，接受两个乘法运算数，返回一个积。<br>&nbsp;&nbsp;如果在函数中动态创建一个对象来存储乘积，并返回对这个新对象的引用，那么下面的计算就会带来内存泄漏：<br>&nbsp;&nbsp;&nbsp;&nbsp; Y=A*B*C <br>&nbsp;&nbsp;因为在这个&#8220;连续&#8221;乘法中，有两个&#8220;乘积对象&#8221;被创建，但是我们丢失了第一次乘法创建的对象的指针。<br>&nbsp;&nbsp;所以这样的做法是不妥的。 <br>&nbsp;&nbsp;也许大师有被问过：那么对于第一种情况我们可不可以返回一个静态(static)对象的引用？书中用了同样的例子来回答：NO。<br>&nbsp;&nbsp;&nbsp;&nbsp; if (A*B == C*D) {//..}<br>&nbsp;&nbsp;如果返回静态对象的引用，上面的判断语句永远得到true值，因为&#8220;==&#8221;号两边的运算结果是指向同一块数据的引用。 </p>
<p>&nbsp;不知道是不是后面又有刨根问题的学生追问：那我能不能用一个静态对象的数组来存放不同此运算的结果？大师懒得举例子了，我猜想也没必要：这样的方案带来的副作用及其开销本身就已经大于原来要解决的问题了吧？ </p>
<p>&nbsp;不要尝试在函数中返回对局部对象(存储于栈)的引用，也不要对动态创建的对象(存储于堆)做同样的蠢事，而如果真有打算、非常渴望、十分想要返回对静态对象的引用，在这么做之前也要考虑清楚会不会有上面例子中的情况出现。<br>&nbsp;至少，返回对象不会有上面列出的种种错误危险，仅仅是有可能带来创建额外对象的开销而已，而这个开销的可能还有可能被排除，如果你用的编译器足够聪明的话。</p>
<p><br><br>Item 22：&nbsp; 将成员变量声明为private<br>-----------------------------------------------------</p>
<p>&nbsp;如果数据成员都是私有的，那么访问这些成员就只能通过函数进行。于是用户就不需要费心考虑到底要用什么方式去访问数据成员：因为只有定义了的函数可以用。 <br>&nbsp;通过定义数据成员为私有，可以实现函数来设计、约束或禁止对这些成员的各种访问(读/写等)。而如果将其设为公有(public)，你将无法得知你的成员会被谁改动，也不知道会是怎样的改动。 <br>&nbsp;而更重要的好处是封装(encapsulation)：可以方便的通过修改函数来改变成员的访问方式；在成员被访问时通知其他对象；实现多线程中的同步等等。<br>&nbsp;封装的好处究其本质，是通过对用户隐藏数据成员来保证类行为的一致性(class invariant)。因为接口被成员访问函数限制了，类的作者也为自己日后修改类的实现留了后路：如果所有的成员都是公有的，对任何代码的修改都有可能影响到外界的使用。(因此Scott说&#8220;Public means unencapsulated, and practically speaking, unencapsulated means unchangeable, especially for classes that are widely used.&#8221;) <br>&nbsp;那么可不可以声明为保护(protected)呢？其实道理和前面的公有是一样的。公有的成员对类的外部完全开放，而保护的成员对类的继承者完全开放。这个就像两个区间：(-infinity, +infinity) 和 (0, +infinity)，两者的大小是一样的。 </p>
<p>&nbsp;从分装的角度，只有两种访问级别：私有，及其他。&nbsp;<br>&nbsp;<br>&nbsp;<br><br>Item 23：&nbsp; 用 non-member、non-friend 替换 member 函数<br>------------------------------------------------------</p>
<p>&nbsp;从面向对象的角度来看，非成员函数更有利于数据的封装。<br>&nbsp;<br>&nbsp;&nbsp;一个数据成员被越少的代码访问到，该成员的封装程度就越高。越少函数可以直接访问一个数据成员，该成员的封装程度就越高。<br>&nbsp;&nbsp;类的数据成员应该定义为私有。如果这个前提成立，那么能够访问一个数据成员的函数便只能是该类的成员函数，或是友元函数。<br>&nbsp;&nbsp;于是为了更好的封装数据，在可以完成相同功能的前提下，应该优先考虑使用非成员并且非友元函数。<br>&nbsp;&nbsp;这里的&#8220;非成员并且非友元函数&#8221;是针对数据成员所在的类而言的，也就是说这个函数完全可以是其他类的成员，只要是不能直接访问那个数据成员就可以。 </p>
<p>&nbsp;从灵活性上来说，非成员函数更少编译依赖(compilation dependency)，也就更利于类的扩展。<br>&nbsp;&nbsp;例：一个类可能有多个成员函数，可能有一个函数需要A.h，另外一个函数要包含B.h，那么在编译这个类时就需要同时包含A.h和B.h，也就是说该类同时依赖两个头文件。<br>&nbsp;&nbsp;&nbsp;如果使用非成员函数，这个时候就可以把这些依赖关系不同的函数分别写在不同的头文件中，有可能这个类在编译时就不需要再依赖A.h或是B.h了。<br>&nbsp;&nbsp;&nbsp;把这些非成员函数分散定义在不同头文件中的同时，需要用namespace关键字把它们和需要访问的类放在一起。<br>&nbsp;&nbsp;// code in class_a.h<br>&nbsp;&nbsp;namespace&nbsp; AllAboutClassA&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; class&nbsp; ClassA&nbsp; { // ..};<br>&nbsp;&nbsp;&nbsp;&nbsp; // ..<br>&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;// code in utility_1.h<br>&nbsp;&nbsp;// ..<br>&nbsp;&nbsp;namespace&nbsp; AllAboutClassA&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; WrapperFunction_1() { // ..};<br>&nbsp;&nbsp;&nbsp;&nbsp; // ..<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;// ..</p>
<p>&nbsp;&nbsp;// code in utility_2.h<br>&nbsp;&nbsp;// ..<br>&nbsp;&nbsp;namespace&nbsp; AllAboutClassA&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; WrapperFunction_2() { // ..};<br>&nbsp;&nbsp;&nbsp;&nbsp; // ..<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;// ..<br>&nbsp;这样一来，虽然这些非成员和类不&#8220;住在&#8221;一个头文件里，它们的&#8220;心&#8221;还是在一起的(在同一个名字空间, namespace, 中)。<br>&nbsp;<br>&nbsp;如果有需要添加新的非成员函数，我们要做的只是在相同的名字空间中定义这些函数就可以，那个类丝毫不会被影响，也即所谓的易扩展性吧。<br>&nbsp;对于类的用户来说，这样的实现方式(指用非成员函数)就更加合理：<br>&nbsp;因为作为类的用户，需要扩展类的时候又不能去修改别人的类(版权？安全性？或者根本就没有源码？)，就算是通过继承该类的方式也不能访问父类的私有数据。 <br><br></p>
<p>Item 24：&nbsp; 若所有参数皆需类型转换，为此函数采用 non-member 函数<br>------------------------------------------------------<br>&nbsp;tag:&nbsp; operator<br>&nbsp;<br>&nbsp;若需要为某个函数的所有参数（包括被this指针所指的那个隐喻参数）进行类型转换，这个函数应该设为 non-member.<br>&nbsp;---------------------<br>&nbsp;class Rational{<br>&nbsp;public:<br>&nbsp;&nbsp;Rational(int num = 0, int denominator = 1); //构造函数刻意不为 explicit; 允许 int-to-Rational 隐式转换<br>&nbsp;&nbsp;...<br>&nbsp;&nbsp;const Rational operator* (const Rational&amp; rhs) const;<br>&nbsp;}<br>&nbsp;<br>&nbsp;Rational oneHalf(1, 2);<br>&nbsp;result = oneHalf * 2;&nbsp;//right&nbsp; &#8594; result = oneHalf.operator*(2); <br>&nbsp;result = 2 * oneHalf;&nbsp;//wrong! &#8594; result = 2.operator*(oneHalf); &#8594; result = operator*(2, oneHalf);</p>
<p>&nbsp;---------------------<br>&nbsp;const Rational operator*(const Rational&amp; lhs, const Rational&amp; rhs){..}&nbsp; //non-member function.<br>&nbsp;若函数不该成为member,不一定要成为friend.<br>&nbsp;<br></p>
<p>Item 25：&nbsp; 考虑写出一个不抛异常的 swap 函数<br>------------------------------------------------------<br>&nbsp;tag: 异常处理&nbsp; <br>&nbsp;&#183;当 std::swap 对你的类型效率不高时，提供一个 swap 成员函数，并确定函数不抛出异常。<br>&nbsp;&#183;如果你提供一个 member swap，也该提供一个 non-member swap 用来调用前者，<br>&nbsp;&nbsp;对于 classes(非 templates)，请特化 std::swap;<br>&nbsp;&#183;调用 swap 时应针对 std::swap 使用using 声明式，然后调用 swap 并不带任何&#8220;命名空间资格修饰&#8221;。<br>&nbsp;&#183;为&#8220;用户定义类型&#8221;进行std templates 全特化是好的，但千万不要尝试在 std 内加入某些对 std 而言全新的东西。<br>&nbsp;<br>&nbsp;<br>&nbsp;std::swap的缺省实现：用了一个中间临时对象，然后两两交换。<br>&nbsp;缺省的方法很简单，而在一些情况下却也很耗资源：比如需要交换的是一个很复杂庞大的对象时，创建/拷贝大量数据会使得这种swap的效率显得非常低下。<br>&nbsp;--------------------------<br>&nbsp;更加适应的实现思路：</p>
<p>&nbsp;在类/模板类(class/class template)中定义一个公有的swap成员函数，这个函数负责实现真正的交换操作。同时，这个函数不能抛出异常。<br>&nbsp;用成员是因为交换操作中可能会需要访问/交换类的私有成员；用公有(public)来限制是为了给下面的辅助函数(wrapper function)提供接口。<br>&nbsp;至于不能抛出异常，有两个原因： <br>&nbsp;&nbsp;一是Item29中所提到的异常安全性(exception-safety)需要不抛出异常的swap来提供保障(更多细节就到拜读29条的时候再记录吧。) <br>&nbsp;&nbsp;二是一般而言高效的swap函数几乎都是对内置类型的交换，而对内置类型的操作是不会抛出异常的。 <br>&nbsp;<br>&nbsp;1. 如果需要使用swap的是一个类(而不是模板类)，就为这个类全特化std::swap，然后在这个特化版本中调用第一步中实现的swap函数。<br>&nbsp;<br>&nbsp;class&nbsp; AClass{<br>&nbsp;public :<br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; swap(AClass &amp;&nbsp; theOtherA){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using&nbsp; std::swap;&nbsp; // 这一句会在稍后的第3点提到<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 通过调用swap来完成该类的特有交换动作 <br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;// .. <br>&nbsp;}<br>&nbsp;<br>&nbsp;namespace&nbsp; std{<br>&nbsp;&nbsp;&nbsp;&nbsp; // 在std名字域内定义一个全特化的swap <br>&nbsp;&nbsp;&nbsp; template &lt;&gt;&nbsp;&nbsp; // 这样的定义说明是全特化 <br>&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; swap &lt; AClass &gt;&nbsp; ( AClass &amp;&nbsp; a, AClass &amp;&nbsp; b){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a.swap(b);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;} <br>&nbsp;如此一来，用户可以直接应用同样的swap函数进行交换操作，而当交换对象是需要特殊对待的AClass对象时，也可以无差别的使用并得到预期的交换结果。 </p>
<p>&nbsp;2. 如果我们需要交换的是模板类，那么就不能用全特化std::swap的方法了，偏特化的std::swap也行不通，因为：<br>&nbsp;&nbsp;C++中不允许对函数进行偏特化(只能对类偏特化)，也 就是说不能写出下面的程序：<br><br>&nbsp;&nbsp;namespace std{&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; // illegal code as C++ doesn't allow partial specialization for function templates<br>&nbsp;&nbsp;&nbsp;&nbsp; template&lt;typename T&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp; void swap&lt; AClassTemplate&lt;T&gt; &gt;(AClassTemplate&lt;T&gt;&amp; a, AClassTemplate&lt;T&gt;&amp; b)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a.swap(b);<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;std名字空间中的内容都是C++标准委员会的老大们定义的，为了保证std内部代码的正常运作，不允许往里头添加任何新的模板、类、方程，重载也不可以。<br>&nbsp;&nbsp;虽然可以像1.那样写出全特化的模板函数，但是企图在std的名字空间添加以下重载的swap(这种重载变相实现了函数的偏特化)(虽然你可以通过编译，但是会埋下隐患)：<br><br>&nbsp;&nbsp;namespace std{<br>&nbsp;&nbsp;&nbsp;&nbsp; template &lt;typename T&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp; void swap (AClass&lt;T&gt;&amp; a, AClass&lt;T&gt;&amp; b)<br>&nbsp;&nbsp;&nbsp;&nbsp; { a.swap(b);}<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;给自己的一个小提醒：因为函数名swap后没有&lt;&gt;，所以不是偏特化，而是对<br>&nbsp;&nbsp;namespace std{<br>&nbsp;&nbsp;&nbsp;&nbsp; template&lt;class _Ty&gt; inline<br>&nbsp;&nbsp;&nbsp;&nbsp; void swap(_Ty&amp; _X, _Ty&amp; _Y)<br>&nbsp;&nbsp;&nbsp;&nbsp; {/*..*/}<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;的重载而已。</p>
<p>&nbsp;基于上面两个原因，一个变通的方法是在该模板类所在的名字空间中编写一个 非成员的函数模板来调用这个公有的接口：<br><br>&nbsp;&nbsp;namespace AClassSpace{<br>&nbsp;&nbsp;&nbsp;&nbsp; template &lt;typename T&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp; void swap (AClass&lt;T&gt;&amp; a, AClass&lt;T&gt;&amp; b)<br>&nbsp;&nbsp;&nbsp;&nbsp; { a.swap(b);}<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;在限定的名字空间中实现函数，是为了避免&#8220;污染&#8221;全局的名字空间。而且，不同的名字空间都可以使用一样的函数名字而不会有冲突。<br>&nbsp;&nbsp;基于前面第23课所学，使用非成员函数也是应该的了。<br>&nbsp;&nbsp;至于为什么要函数模板，那就匪常D简单：因为要交换的是模板@#￥% </p>
<p>&nbsp;pimpl也即pointer to implementation,当需要交换两个复杂且臃肿的对象时，可以先用两个指针分别指向着两个对象<br>&nbsp;之后对这些对象的操作，包括交换，就只需要通过这两个指针来进行(交换两个指针的值便实现了对象的交换)。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/Euan/aggbug/109774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:50 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective C++  3. 资源管理类</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109772.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:48:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109772.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109772.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109772.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109772.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109772.html</trackback:ping><description><![CDATA[<p>========================<br>Effective C++&nbsp;&nbsp; 资源管理类（resource-managing classes）<br>书作者：Scott Meyers<br>原笔记作者：Justin <br>========================<br><br></p>
<p>Item 13 ：以对象管理资源<br>---------------------------------------<br>&nbsp;tag：auto_ptr&nbsp;&nbsp; shared_ptr&nbsp;&nbsp; RAII&nbsp;&nbsp;&nbsp; RSCP&nbsp;&nbsp; 智能指针<br>&nbsp;<br>&nbsp;要利用对象来管理资源（可以是分配出来的内存、互斥量等）。这样可以避免因为写代码时的疏忽而导致的资源流失（可以是内存的泄漏，也可能是忘了解锁）。</p>
<p>&nbsp;将释放资源的代码放在对象的析构函数中，只要对象被析构，就可以保证资源被完整释放。 <br>&nbsp;C++的析构函数自动调用机制(C++&#8217;s automatic destructor invocation)能够保证当对象脱离控制时该对象的析构函数被调用（比如一个对象在某函数内部定义，那么在函数执行结束后，这个对象的析构函数会被自动调用以销毁此对象）。 <br>&nbsp;是个好办法，至少对我这样时常丢三落四的人来说是个福音。何况这样的对象大多数情况下不用自己写，auto_ptr（智能指针, smart pointer的一种）就可以胜任。 </p>
<p>&nbsp; std::auto_ptr&lt;void *&gt;&nbsp; pMem(AllocAndInitMemory());<br>&nbsp; <br>&nbsp;高亮的部分即说明了auto_ptr的用法，也附带示范了所谓的RAII(Resource Acquisition Is Initialization)资源取得时机便是初始化时期。<br>&nbsp;<br>&nbsp;这里的auto_ptr就像是个陪女友逛街的可怜家伙，女朋友（用户）说要什么，auto_ptr就乖乖的去买来（申请），想要用的时候就赶紧拿出来给女友用，哪怕她忘了曾经买了某件衣服，auto_ptr还是会老老实实地送回家里，不用担心会弄丢或是搞脏。嗯，有点做上帝的感觉吧？ </p>
<p>&nbsp;不过，没有什么是十全十美的。大师马上就说到了用对象管理资源的问题，好吧，应该说是需要注意的地方：<br>&nbsp;首先不能用一个以上的对象，比如说auto_ptr，管理同一个资源。这个很好理解，就像一个漂亮的女孩子脚踏多条船（同时定义了多个auto_ptr），但是她必须要小心，不能让多个对象参加同一个约会：今天逛街的时候喊上了小张就不能叫小王，明天一起吃饭如果约了小李就别再和小赵定时间：一个资源一次只能让一个对象来管理。<br>&nbsp;这样的规则用在auto_ptr上就是书中奇怪的&#8220;=&#8221;行为：两个auto_ptr对象A和B，A=B运算的结果是A接管了B管理的资源，B指向的是null。（如果记不起来了，具体代码见书）这样的行为很像是传递：小李陪女朋友逛完街后，送她到人民广场，在那里小陈约好了和她一起看电影。到了人民广场之后小陈搂着女人开心的走了，只剩下小李一个孤单的背影，杯具啊杯具&#8230;&#8230; </p>
<p>&nbsp;为了避开这种尴尬且极易被误解的&#8220;=&#8221;操作，出现了以shared_ptr为代表的reference-counting smart pointer(RCSP)（引用计数型智能指针？）。它们可以&#8220;共事一夫&#8221;，由一个公用的reference counter来计数，某个shared_ptr在准备抛弃一个资源时先参考reference counter的值，如果非零，说明还有其他的&#8220;姐妹&#8221;在罩着资源，不需要也不可以释放该资源；如果值为零，说明当前只有她一个人了，于是真正的担当起smart pointer的责任：释放资源。 <br>&nbsp;<br>&nbsp; std::shared_ptr&lt;void *&gt;&nbsp; pMem(AllocAndInitMemory());</p>
<p>&nbsp;重点不在于是用auto_ptr还是shared_ptr还是其他的什么东东，而是要领会精神——使用对象来管理资源。比如说用string对象来管理一个字符串而不是手工申请一片内存然后用个指针&#8220;吧唧&#8221;粘上去就开始用了。 <br>&nbsp;不能再动态分配而得的 array 上使用 auto_ptr 或 tr1::shared_ptr，因为两者在析构的时候调用的是delete而不是delete[].<br>&nbsp;要拥有针对数组而设计的，可用 boost::scoped_array 和 boost::shared_array classes.<br>&nbsp;<br><br><br>Item 14: 在管理类中小心 coping 行为<br>--------------------------------------------<br>&nbsp;tag： RAII&nbsp;&nbsp; <br>&nbsp;<br>&nbsp;RAII守则：资源在构造期间获得，在析构期间释放。<br>&nbsp;<br>&nbsp;RAII对象被复制：<br>&nbsp;&nbsp;&#183;不允许拷贝。当资源本身不能复制时，对象可以说&#8220;不&#8221;。怎么做？回到Item6&#8230;&#8230; <br>&nbsp;&nbsp;&#183;使用Reference-Count（引用计数），可以用上节说到的shared_ptr来干这个事，这里顺带介绍了shared_ptr提供的一个接口：一个可以在构造对象时定义的delete操作：如果对象是内存就是释放，如果对象是锁就是解锁。 <br>&nbsp;&nbsp;&#183;直接复制。别人有什么，你就直接原封不动也复制一份。如果是内存的话说得过去，如果是锁，我想还是不能这样乱用哈。 <br>&nbsp;&nbsp;&#183;移交所有权。这个不算是真正意义的复制，移交手续而已。最典型的例子就是auto_ptr的复制行为。 </p>
<p><br><br>Item 15： 在资源管理类中提供对原始资源的访问<br>-------------------------------------------------<br>&nbsp;tag：返回原始资源<br>&nbsp;<br>&nbsp;&#183;在使用对象管理资源的同时也要留出接口给那些需要绕过对象而直接访问资源的人。<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;写个函数暴露出指向资源的指针就可以。书里讲得更多的是用怎样的函数：<br>&nbsp;<br>&nbsp;显式转换(explicit conversion) <br>&nbsp;可以实现一个get函数，或是*、-&gt;运算，返回指向资源的指针。<br>&nbsp;<br>&nbsp;隐式的转换函数(implicit conversion)，<br>&nbsp;<br>&nbsp;但是个人觉得实际工作中应该是不提倡这样做的，因为隐式的转换极有可能发生在编程者没有意识的情况下，导致后面的代码出错。<br><br>&nbsp;class Font {<br>&nbsp;public:<br>&nbsp;//&nbsp; ..<br>&nbsp;// implicit conversion function<br>&nbsp;&nbsp; operator FontHandle() const { return f; }<br>&nbsp;//&nbsp; ..<br>&nbsp;};<br>&nbsp;上面代码的应用如下，f本身为Font类型，(changeFontSize第一个参数为FontHandle)，但是由于隐式转换，类型变成了FontHandle。<br><br>&nbsp;Font f(getFont());<br>&nbsp;int newFontSize;<br>&nbsp;//..<br>&nbsp;// implicitly convert Font to FontHandle<br>&nbsp;changeFontSize(f, newFontSize); <br>&nbsp;<br>&nbsp;为了能兼容更多的API，需要留出接口提供对资源的直接访问。 <br>&nbsp;隐式或显示主要取决于RAII class 被设计执行的特定工作，以及它被使用的状况。<br>&nbsp;<br>&nbsp;<br>Item 16 ： 成对使用new和 delete时采用相同形式<br>---------------------------------------------------<br>&nbsp;tag：&nbsp; new delete<br><br></p>
<p>&nbsp;&#183;用new分配一个内存对象时，语法格式是new a;用delete释放一个内存对象时，语法格式是delete a; <br>&nbsp;&#183;用new分配一组内存对象时，语法格式是new a [num_of_elem];&nbsp;用delete释放一组内存对象时，语法格式是delete [] a; <br>&nbsp;<br>&nbsp;new或是delete包含了两个阶段： </p>
<p>&nbsp;new：申请并分配内存空间；调用构造函数构造即将使用空间的对象 <br>&nbsp;delete：调用析构函数析构使用空间的对象；释放内存 <br>&nbsp;<br>&nbsp;分配内存给一组对象的时候，编译器一般会在这一片内存前端(或是其他什么地方)插入一小段信息，用来标明这片内存是给多少个对象的，然后反复调用构造函数来创建这一组对象。当用delete []的时候，释放内存的操作就会以该信息为依据，反复调用对象的析构函数对这组对象进行释放。(下面的[n]就是这段信息)<br>&nbsp;[n][MEM]<br>&nbsp;而如果只是分配内存给一个对象，这段信息就不存在了。直接在这片内存上应用析构函数。<br>&nbsp;<br>&nbsp;于是用delete []去释放new的内存，或是用delete去释放new []的内存，都会造成不可预计的后果。 <br></p>
<p><br><br>Item 17 ： 将new语句单独写<br>----------------------------------<br>&nbsp;tag：&nbsp;&nbsp; newed<br>&nbsp;<br>&nbsp;processWidget(std::tr1::shared_ptr&lt;Widget&gt;(new Widget), priority());这行语句有问题，这个复杂的参数表包含了三个动作：</p>
<p>&nbsp;&nbsp;&#183;new 一个 Widget <br>&nbsp;&nbsp;&#183;用new的Widget做为参数执行share_ptr的构造函数 <br>&nbsp;&nbsp;&#183;执行priority <br>&nbsp;C++的某个编译器可能为了效率而自作主张，导致这三个动作的执行顺序是不确定的！因此上面的动作执行顺序可能是这样的：</p>
<p>&nbsp;&nbsp;&#183;new 一个 Widget <br>&nbsp;&nbsp;&#183;执行priority <br>&nbsp;&nbsp;&#183;用new的Widget做为参数执行share_ptr的构造函数&nbsp;<br><br>&nbsp;这个时候如果priority的执行出错而引发异常，就会发生内存泄漏(Memory Leak)，因为new出来的Widget再也无法跟踪了。</p>
<p>&nbsp;而解决方法也很简单，不要妄图一行写完所有程序，分开来老老实实写就是了：<br><br>&nbsp;&nbsp;&nbsp; std::tr1::shared_ptr&lt;Widget&gt; pw(new Widget);<br>&nbsp;&nbsp;&nbsp; processWidget(pw, priority());&nbsp;&nbsp;<br><br>&nbsp;VC++下不会。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/Euan/aggbug/109772.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:48 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109772.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective C++ 2. 构造、析构、赋值运算</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109771.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:44:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109771.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109771.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109771.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109771.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109771.html</trackback:ping><description><![CDATA[<p>========================<br>Effective C++&nbsp;&nbsp; 构造、析构、赋值运算<br>书作者：Scott Meyers<br>原笔记作者：Justin <br>========================</p>
<p><br>Item 5、6&nbsp;&nbsp; ：C++默认编写的函数<br>-----------------------------------------<br>&nbsp;tag： 默认拷贝函数&nbsp; copy assignment<br>&nbsp;<br>&nbsp;如果没有定义类的构造函数/拷贝函数/拷贝构造函数/析构函数，编译器会自作主张帮你定义这些函数，而且还是public外加inline的。(估计这是最冷的冷饭了)<br>&nbsp;帮写拷贝函数时仅限于帮你处理一下诸如int的内嵌类型(build-in type)以及自身就有拷贝函数的类对象，再复杂一点的就搞不定了。 </p>
<p>&nbsp;有时候需要禁止某个类对象的拷贝操作，就要避免以上函数的自动生成，需要自己定义这些函数，并且有以下特性 <br>&nbsp;private <br>&nbsp;只是声明，不给予实现(give no implementation) <br>&nbsp;当然要是还是觉得麻烦，干脆直接继承Boost的noncopyable(自己写一个类似的也可以)，就可以不费吹灰之力的获得不能拷贝的特性。 <br><br></p>
<p>Item 7&nbsp; ：为多态基类声明virtual析构函数<br>--------------------------------------------<br>&nbsp;tag：&nbsp;多态（polymorphism）&nbsp;虚函数&nbsp; virtual function&nbsp;&nbsp;析构函数</p>
<p>&nbsp;&#183;polymorphic base classes 应将析构函数定义为虚函数(virtual function)<br>&nbsp;&#183;当class内至少含一个virtual函数时，才声明virtual析构函数。<br>&nbsp;<br>&nbsp;否则在通过基类指针析构一个子类对象的时候，因为没有虚表(V Table)对析构函数的指引，对象的基类部分是被毁了，对象的子类部分却没办法得到析构(没有了虚表中的说明它根本不知道有子类的析构函数来析构子类部分！)，从而导致内存泄漏。 </p>
<p>&nbsp;virtual函数实现：<br>&nbsp;&nbsp;要实现virtual，vptr（virtual table pointer）指针指向一个向函数指针构成的数组，称为vtbl（virtual table）；<br>&nbsp;&nbsp;带有virtual函数的class都有一个对应的vtbl，对象调用virtual函数时，实际被调用的函数取决于该对象的vptr所指的那个vtbl（在其中寻找适当的函数指针）<br>&nbsp;<br>&nbsp;假设基类指针在析构时要做的事情是奉命救人：指针啊指针，XX地发大水了，你赶紧去把张三一家人(子类对象)救出来。<br>&nbsp;指针拿着指令(析构函数)就找到了张三(张三家家长，子类对象的基类部分)：张三是吧，你一家人都在这里了吧，跟我走吧。(析构吧，回空余内存里去吧@#￥%)<br>&nbsp;如果张三已经没有其他亲戚了，他就是张三家的全部人(这个对象其实就是基类对象)。很简单，直接跟着指针走就完事了。<br>&nbsp;如果张三还有个小弟叫张三疯(张三家除张三外的一部分，即子类对象的子类部分)，张三就必须拿着族谱(V Table)跟指针说哎呀这是我弟啊，把他也析构了吧，张三家对您感恩不尽&#8230;&#8230;也很简单，一并带走就完成任务了(完整析构/释放了整个对象)<br>&nbsp;如果，张三没了族谱，记不得有个张三疯弟弟，傻乎乎的跟了指针走，大家也以为张三一家都被救出来了。灾区里就只剩下张三疯一个人在疯跑了(memory leak) </p>
<p>&nbsp;另一方面，如果不准备把某个类用作多态中的基类，就没必要定义析构函数为虚函数。这样除了增加代码大小开销外(因为需要空间存放虚表)，没有任何好处。(这个倒是很多书上没说过的) </p>
<p>&nbsp;如果张三家本来就只有张三一个人，他就没必要还要带着个空白的族谱了。逃命的时候能扔就扔嘛。 </p>
<p><br>&nbsp;最后提到的一点和抽象类有关。抽象类是有纯虚函数的类，只能作为接口，不能有自己的对象。在设计软件框架的时候会有需要用到抽象类的时候。可是如果有这么一个类，需要设计为抽象类，但是却找不到一个成员函数可以拉去做纯虚函数，那么这个时候就把析构函数定义为纯虚函数好了。只是同时还要给它一个空的实现。 </p>
<p>&nbsp;class::~class() {} </p>
<p><br>&nbsp;</p>
<p>Item 8 ：别让异常逃离析构函数<br>-------------------------------------------<br>&nbsp;tag：异常 try catch&nbsp; 析构函数<br>&nbsp;<br>&nbsp;&#183;在析构函数中不能抛出异常。 若被析构函数调用的函数可能会抛出异常，析构函数应该捕捉任何异常，并吞下（不传播）或结束程序。<br>&nbsp;&#183;若一个操作可能出现异常，客户需要对这个异常作出反应。class应提供一个普通函数来执行该函数，而不是在析构函数内调用。</p>
<p>&nbsp;异常不像函数调用，一旦抛出(throw)，就回不来了。<br>&nbsp;如果在析构函数中抛出了异常，有一部分释放/摧毁对象成员的操作可能就无法进行。因为某个异常，导致了所在的析构函数无法完成全部析构任务。 </p>
<p>&nbsp;可是要是真的需要对析构动作中出现的错误/异常进行处理咋办？书中自有解决方案：从差的到好的。 </p>
<p>&nbsp;在析构函数内布置catch，一旦发生异常就会被捕获，然后简单调用std::abort自杀<br>&nbsp;&nbsp;点评：干脆是干脆了，但是这样猝死会不会有点太突然？ <br>&nbsp;也是在函数内布置catch，但是遇到异常就把它直接吃到肚子里(大师语:Swallow the exception)。<br>&nbsp;&nbsp;点评：一般不该这样处理，毕竟发生了不好的事。但如果真的想要程序继续带伤上阵，也确定这样不会有问题，那也不会有人有意见。 <br>&nbsp;&nbsp;<br>&nbsp;除了在函数里布置catch，并采用以上任一方法，另外实现一个可供用户调用的函数接口，用来处理这些有可能出错的析构工作。<br>&nbsp;&nbsp;点评：大师给予这个方案高度的评价，因为这样不但有以上两种方法的效果，还给用户一个参与处理异常的机会(调用接口函数)。如果用户没有(或者忘记)用该函数处理析构的动作，那么析构函数也会自觉挑起这个任务。而这个时候如果还出问题，用户也没有什么理由来责怪了：是你自己不想要管的！ </p>
<p><br><br>Item 9 ：不在构造和析构过程中调用 virtual 函数<br>-----------------------------------------------------<br>&nbsp;tag：<br>&nbsp;<br>&nbsp;拥有虚函数的类就有虚表，虚表可能会引发子类相应虚函数的调用，在这些调用中有可能对某些子类对象成员的访问。<br>&nbsp;在构造一个子类对象时：&nbsp;&nbsp; 开始构造父类部分 -&gt; 完成父类部分并开始构造子类部分 -&gt; 完成子类部分&nbsp;&nbsp;&nbsp; (完成整个构造工作)<br>&nbsp;析构一个子类对象的时：&nbsp; 开始析构子类部分 -&gt; 子类析构完毕并开始析构父类部分 -&gt; 完成析构父类部分(完成整个析构工作)</p>
<p>&nbsp;在构造函数的第一步，子类对象成员还不存在，调用虚函数有可能会访问不存在的子类成员；<br>&nbsp;哪怕到了第二步，因为子类对象还没有完全构造完毕，此时调用虚函数也是危险的。事实上在整个构造过程中，该对象都被视作一个父类对象。<br>&nbsp;反过来也是同样道理，在析构函数的第一步，子类成员已经开始销毁，不能调用虚函数；到了第二步，整个子类部分都没有了，更不能用虚函数了。<br>&nbsp;而在整个析构过程中，该对象也是被看作是父类对象的。 <br>&nbsp;<br>&nbsp;确保虚函数不会在对象构造/析构过程中被调用：</p>
<p>&nbsp;方法之一就是用参数传递代替虚函数机制。<br>&nbsp;把可能被构造函数调用的虚函数改成非虚函数，然后让父类的构造函数将需要的信息/数据通过参数传递给子类构造函数。<br>&nbsp;&nbsp;Class Parent<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp; public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Parent();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Parent( const&nbsp; std:: string &amp;&nbsp; WordsFromChild){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DoStuff(WordsFromChild);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // .. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void&nbsp; DoStuff( const&nbsp; std:: string &amp;&nbsp; WordsFromChild);<br>&nbsp;&nbsp;} &nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; Class Child :&nbsp; public&nbsp; Parent<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;public :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Child( /**/ /* some parameters here */ ) : Parent(TellParent( /**/ /* some parameters here */ )) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // .. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;private :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static&nbsp; std:: string &amp;&nbsp; TellParent( /**/ /* some parameters here */ );<br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp; 也许看到这里会想：要是TellParent()中访问了未被初始化的子类成员呢？那不也是一样有问题么？<br>&nbsp;&nbsp;&nbsp;&nbsp; 注意，这就是为什么前面有个static限定的原因。因为静态函数可以在对象的初始化阶段就开始工作，更详细的描述看这里。<br>&nbsp;子类的虚表在子类的构造函数时生成，所以在父类的构造函数中调用虚函数使用的是父类的版本。<br>&nbsp;子类和父类对象都会有自己的虚表，里面安置的是自己版本的虚函数实现。</p>
<p><br><br>Item 10-12 ：&nbsp; 拷贝运算符<br>--------------------------<br>&nbsp;tag：assignment operator（赋值运算符）&nbsp; 自我赋值&nbsp; copying函数<br>&nbsp;<br>&nbsp;&#183;令赋值（assignment）操作符返回一个reference to *this。 可实现（a=b=c)。<br>&nbsp;&#183;处理自我赋值<br>&nbsp;&#183;构造函数用来初始化新对象，而 assignment操作符只施行于已初始化对象身上。<br>&nbsp;&#183;copying函数应该确保复制&#8220;对象内所有成员变量&#8221;和&#8220;所有 base class成分&#8221;<br>&nbsp;<br>&nbsp;1、<br>&nbsp;&nbsp;在函数入口检查是否属于自拷贝(例如：检查指针是否指向同一片内存)，如果是，啥也不干直接返回。否则属于正常情况的拷贝。<br>&nbsp;&nbsp;这样解决了self-assignment-unsafe的问题，但是没能避免exception-unsafe。 <br>&nbsp;&nbsp;<br>&nbsp;2、<br>&nbsp;&nbsp;第二种方法比较简单，只是整理一下指令的顺序。但是却同时解决了自赋值和抛出异常带来的问题。继续无耻的抄写代码一段：&nbsp;<br><br>&nbsp;&nbsp;&nbsp;Widget&amp;&nbsp;&nbsp;Widget::operator=(const Widget&amp; rhs)<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Bitmap *pOrig = pb;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // remember original pb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pb = new Bitmap(*rhs.pb);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // make pb point to a copy of *pb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete pOrig;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // delete the original pb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return *this;<br>&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;这样的做法在解决以上两个问题的同时却也降低了执行的效率：不论什么情况，这个赋值函数都要创建一个新的Bitmap对象。<br>&nbsp;&nbsp;第一种方法的额外支出：判断语句必然地引入了程序的分支(branch)，于是指令的预取(prefetch)、缓冲(caching)、流水线处理(pipelining)的效率就会被降低。 <br>&nbsp;3、<br>&nbsp;&nbsp;Copy And Swap。改赋值为交换。<br><br>&nbsp;&nbsp;&nbsp;void swap(Widget&amp; rhs);&nbsp;&nbsp;&nbsp;//交换*this 和rhs的数据；<br><br>&nbsp;&nbsp;&nbsp;Widget&amp;&nbsp; Widget::operator=(const Widget&amp; ths)<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Widget temp(ths);&nbsp;&nbsp;//为rhs数据制作一份副本<br>&nbsp;&nbsp;&nbsp;&nbsp;swap(temp);&nbsp;&nbsp;&nbsp;&nbsp;//将*this数据和上述副本的数据交换。<br>&nbsp;&nbsp;&nbsp;&nbsp;return *this;<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;Widget&amp;&nbsp; Widget::operator=(Widget rhs)&nbsp;&nbsp;&nbsp; // rhs is a copy of the object<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; // passed in — note pass by val<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; swap(rhs);&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; // swap *this's data with the copy's<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return *this;<br>&nbsp;&nbsp;&nbsp;}利用参数传值，隐性的构造了一个Widget对象。然后将新对象和本对象中的数据成员交换，达到为本对象赋值的效果。<br>&nbsp;&nbsp;新的临时对象在跳出函数后自动销毁。刚才说的两个unsafe，都不会存在。<br>&nbsp;&nbsp;这样开销较大了，无论什么时候都要构造新的对象。用swap来完成赋值的做法有点逻辑混淆。但这样做很有可能让编译器生成更有效率的代码。</p>
<p>&nbsp;---------------------------<br>&nbsp;&nbsp;如何保证在赋值/拷贝的时候能够将所有的成员完整拷贝过去？<br>&nbsp;对于简单的数据成员，编译器自动生成的拷贝函数可以保证一个不漏都帮你拷贝；<br>&nbsp;如果是比较复杂的成员(比如说指向一片内存空间的指针)，编译器就没有足够的智商把这些成员拷贝到另外一个对象中去了。 </p>
<p>&nbsp;在增加类成员以后记得更新拷贝函数，以免拷贝不完全。 <br>&nbsp;子类的拷贝函数把自己的成员都拷贝了，但是却漏了把父类对象的成员拷贝到新的对象中。 在子类的拷贝函数中调用父类的拷贝函数</p>
<p>&nbsp;&nbsp; Widget&amp;&nbsp; Widget:: operator&nbsp;&nbsp; =&nbsp; (Widget src)&nbsp; <br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; swap(src);&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; // copy-and-swap <br>&nbsp;&nbsp;&nbsp;&nbsp; WidgetParent:: operator&nbsp;&nbsp; =&nbsp; (src);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // invoking the parent's copy assignment operator <br>&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;&nbsp; * this ;<br>&nbsp;&nbsp;} </p>
<p>&nbsp;&nbsp;最后的最后，通常来说在拷贝函数和拷贝构造函数中的实现大多相同，<br>&nbsp;不要在拷贝函数中调用拷贝构造函数或者反之。如果真的需要避免代码的重复，大可定义一个私有的函数来负责前面两者相同的部分。 </p>
<img src ="http://www.cppblog.com/Euan/aggbug/109771.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:44 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109771.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effective C++  1. 习惯C++</title><link>http://www.cppblog.com/Euan/archive/2010/03/15/109770.html</link><dc:creator>Euan</dc:creator><author>Euan</author><pubDate>Mon, 15 Mar 2010 14:43:00 GMT</pubDate><guid>http://www.cppblog.com/Euan/archive/2010/03/15/109770.html</guid><wfw:comment>http://www.cppblog.com/Euan/comments/109770.html</wfw:comment><comments>http://www.cppblog.com/Euan/archive/2010/03/15/109770.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Euan/comments/commentRss/109770.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Euan/services/trackbacks/109770.html</trackback:ping><description><![CDATA[<p>毕业设计中途重读了几本以前看过的书，需要做些笔记做日后查阅时使用。<br>偶然看到 Justin 写的《Effective C++》笔记，为方便就结章转载在此。<br><br>========================<br>Effective C++&nbsp;&nbsp; C++<br>书作者：Scott Meyers<br>原笔记作者：Justin : http://www.cppblog.com/note-of-justin/<br>========================<br><br></p>
<p>Item 1 ：C++是一个语言联邦<br>--------------------------<br>&nbsp;tag: c c++组成<br><br>&nbsp;C &nbsp;<br>&nbsp;&nbsp;区块（blocks）、语句（statements）、预处理器（preprocessor）、内置数据类型（built-in data types）、数组（arrays）、指针（pointers）。<br>&nbsp;&nbsp;<br>&nbsp;Object-Oriented C++<br>&nbsp;&nbsp;classes（包括构造、析构）、封装（encapsulation）、继承（inheritance）、多态（polymorphism）、virtual函数（动态绑定）<br><br>&nbsp;Template C++<br>&nbsp;&nbsp;泛型编程（generic programming）&nbsp; Template metaprogramming（TMP，模版元编程）<br><br>&nbsp;STL<br>&nbsp;&nbsp;容器（containers）、迭代器（iterators）、算法（algorithms）、函数对象（function objects）<br>&nbsp;&nbsp;<br>&nbsp;每个语言都有自己的次规则</p>
<p><br>Item 2&nbsp; ：用const、enum和模板inline推翻#define的统治 <br>---------------------------------------------------<br>&nbsp;tag: const enum inline #define<br>&nbsp;<br>&nbsp;需要定义常量时，不要用#define，改用const修饰的变量或是用enum吧 <br>&nbsp;要想写一些简短小函数时，别考虑#define啦，改用template+inline吧 </p>
<p>&nbsp;原因是若为浮点变量，用const减小了代码大小，同时还使得封装(encapsulation)变得可行（宏被定义后，在之后的编译过程都有效，除非undef ），而且，在调试的时候，因为const定义的变量是会加在符号表(Symbol Table)的，就比define常量要方便跟踪了(在预处理阶段，常量的名字就已经被替换掉了)<br>&nbsp;在一些特定的情况下(编译器不允许&#8220;static整数型class常量&#8221;完成&#8220;in class 初值设定&#8221; )，如果不能用const取代#define，就用enum。除了不能获取一个enum的地址之外，这种方法和const的用法其实差不多。 <br>&nbsp;可以取一个const的地址，不能取一个enum的地址，通常不能取一个#define的地址。<br>&nbsp;<br>&nbsp;inline函数和宏有个共同的地方，他们都不会有函数调用的栈的开销。再喊上模板(template)来帮忙，就不用去考虑实际调用时的参数类型。<br><br><br></p>
<p>Item 3&nbsp; ：尽可能使用const<br>--------------------------------------------------<br>&nbsp;tag: const&nbsp; non-const&nbsp; conceptual constness&nbsp; mutable<br>&nbsp;<br>&nbsp;&#183;首先要知道const可以通用在对象上，函数参数和返回值上，甚至是用在限制函数本身。 <br>&nbsp;&#183;const 出现在星号左边，表式被指物是常量；出现在星号右边，指针自身是常量；<br>&nbsp;&#183;两个成员函数如果只是常量性（constness）不同，可以被重载。<br>&nbsp;&#183;const和non-const成员函数的实现等价时，可以用non-const版本调用const版本避免代码重复。<br>&nbsp;<br>&nbsp;&nbsp; &nbsp;Compilers enforce bitwise constness, but you should program using conceptual constness. </p>
<p>&nbsp;这里有提到constness(常量性)的两个门派: bitwise学院派和conceptual实用派。<br>&nbsp;bitwise constness阵营应该都是很学究的，这里认为如果一个函数被声明是const，你就绝对不能修改对象里的任何成员(static成员除外)。<br>&nbsp;主张conceptual constness流的当然都比较好说话，虽然你是const党，但需要的时候，还是应该有例外的嘛。正所谓人无完人，const也没有绝对的const～ <br>&nbsp;<br>&nbsp;&nbsp;conceptual constness可以这样解释：具备conceptual constness的对象/函数，其行为对于该对象/函数以外的数据是const的，不会篡改别人的东东。但是不保证它&nbsp;不会修改对象/函数内部的成员：当这些成员用mutable修饰的时候，我们可以在一个const函数中修改这些mutable成员的值。 <br>&nbsp;所以说这样的constness是概念上的，实际上在这样的函数中有可能改变了一些变量的值，只不过没有与它声称的constness矛盾而已。 </p>
<p>&nbsp;用mutable限定的对象，哪怕是在const函数里，一样可以修改！ </p>
<p>&nbsp;和const有关的还有在const和非const对象间的转换问题(用const_cast和static_cast完成两个方向的转换)，不过层次太高，我还没能看到有需要用的地方<br><br>&nbsp;const char&amp; operator[](size_t position) const {<br>&nbsp;&nbsp;...<br>&nbsp;}<br>&nbsp;char&amp; operator[](size_t position)&nbsp;&nbsp; //调用已经实现的const op[]<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return const_cast&lt;char&amp;&gt; (&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//将const op[] 的返回值中移除 const<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static_cast&lt;const CClass&amp;&gt;(*this)&nbsp;&nbsp; //将*this转型为 const，指明调用的是const版本的op[]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[position] );<br>&nbsp;}</p>
<p><br>Item 4&nbsp; ：对象初始化<br>--------------------------------------------<br>&nbsp;tag：local static ， 初始化列表， 赋值（assignment）<br>&nbsp;<br>&nbsp;&#183;对于内建的对象类型，手工初始化。<br>&nbsp;&#183;对于对象自身的成员，推荐的方法是在构造函数的初始化列表。<br>&nbsp;&#183;以logcal static 对象替换 non-local static对象，以避免&#8220;跨编译单元之初始化次序&#8221;问题。<br>&nbsp;<br>&nbsp;赋值（assignment）的效率要比初始化（initialization）低，因为前者先构造了对象再对他们赋值，在构造的同时就也把值赋了。这里还没加上拷贝构造函数的可能开销，还有一些类型如const变量、引用(reference)是不能用赋值的形式&#8220;初始化&#8221;的&#8230;&#8230;<br>&nbsp;如果在初始化某个对象的时候，有对其他对象是否有初始化的依赖(对不起，这里有点拗口)，一定要确保其中所依赖的对象已经初始化完毕。<br>&nbsp;<br>&nbsp;当不同的对象的初始化存在相互依赖时，某个对象没有初始化有可能导致另外一个对象初始化的失败。<br>&nbsp;当初始化涉及到非局部静态对象(non-local static object)时，问题更加明显：非局部静态对象如果定义在不同的文件中，他们就有可能位于不同的编译单元(translation unit)，因为这些对象到底谁先被初始化是不可预知的。<br>&nbsp;编译单元（translation unit）：产出单一目标文件（single object file）的那些源码，通常为单一源码文件加上所包含的头文件。</p>
<p>&nbsp;解决此类问题的一个方法是：把非局部静态对象转换为局部静态对象(local static object)，也就是把它的定义放在一个函数里。然后紧接着在这个函数返回该对象的引用。C++语言规定在调用一个含有局部静态对象的函数时，其中的所有局部静态对象都必须初始化。这个方法就是利用这一特性，将原本对一个非局部静态对象的访问，转换为对一个函数的调用，这个函数会返回该静态对象的引用，并且保证这个对象已经被初始化了。</p>
<p>&nbsp;如果需要初始化一个非局部静态对象，就把它放到一个函数里，让这个函数简单的返回这个对象的引用。<br>&nbsp;</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/Euan/aggbug/109770.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Euan/" target="_blank">Euan</a> 2010-03-15 22:43 <a href="http://www.cppblog.com/Euan/archive/2010/03/15/109770.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>