《Effective C++ 》学习笔记——条款04
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
一、 Accustoming Yourself to C++
Rules 4: Make sure that objects are initialized before they‘re used
条款4:确定对象被使用前已先被初始化
一、原因:
关于”将对象初始化“这事,C++似乎反复无常,因为你如果这么写:
int x;
在某些情况下,它会被初始化(为0),有时候不会,这乍一看不重要,其实对于灵活的C++,这往往会扮演致命的角色。而且在不同情况下,这个规则也不同。
通常如果使用C part of C++ 而且初始化可能招致运行期成本,那么就不保证发生初始化,但若进入non-C parts of C++ 规则就会有些变化。
典型的例子就是,来自C part of C++ 的array 不保证它的内容被初始化,而来自STL part of C++的 vector 却有这个保证。
二、解决:
如此解决这个问题呢?就如同题目所讲,永远在使用对象前先将它初始化。
1.初始化
对于内置类型,初始化可以通过手工,例如:
int x = 0 ;
const char* text = "A C-style string";
读取流的方式初始化:
double d;
std::cin>>d;
而对于非内置类型,初始化的责任就在 constructors(构造函数)身上,规则很简单:确保每一个构造函数都将对象的每一个成员初始化。
2.别混淆了 assignment(赋值) 和 initialization(初始化)
同样看例子:这是一个用来表现通讯薄的class,其构造函数如下
class PhoneNumber {...}; class ABEntry { public: ABEntry( const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones ); private: std::string theName; std::string theAddress; std::list<PhoneNumber> thePhones; int numTimesConsulted; }; ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones) { theName = name;<span style="white-space:pre"> </span>// 这些都是赋值,并非初始化 theAddress = address; thePhones = phones; numTimesConsulted = 0; }
正如注释上所说,ABEntry函数里的内容都是赋值行为,并非是初始化行为,因为C++规定了,对象的成员变量初始化动作发生在进入构造函数本体之前。更准确的来说是发生于这些成员的default构造函数被自动调用之时。
对于构造函数比较好的写法是,使用所谓的 member initialization list(成员初值列)替换赋值动作:
ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones) :theName(name),<span style="white-space:pre"> </span>// 这些都是初始化 theAddress(address), thePhone(phones), numTimesConsulted(0) {<span style="white-space:pre"> </span>// 构造函数本体不必有任何动作 }
对于内置类型,比如例子中的 numTimesConsulted来说,初始化和赋值的效率基本相同,但是为了一致性,最好也通过成员初值列来进行初始化,甚至如果你想要default构造一个成员变量,都可用指定 nothing(无物)作为初始化实参即可,比如:
ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones) :theName(), theAddress(), thePhone(), numTimesConsulted(0) { }
规则:
<1> 编译器会为 user-defined types (用户自定义类型) 之成员变量自动调用default构造函数——如果那些成员变量在”成员初值列“中没有被指定初值的话。所以,最好在初值列中列出所有成员变量,可以无须初值,这样可以记住都有哪些成员变量,防止遗漏。
<2>有些情况下即使面对的成员变量属于内置类型,也一定要使用初值列,如果成员变量是const 或 reference,它们就一定要初值,不能被赋值。
这两个规则总结下来,就是——总是使用成员初值列,列出所有成员变量。
三、针对一些现象的解决
1.现象: 许多classes 拥有多个构造函数,每个构造函数有自己的成员初值列。这样就会导致很多的重复动作。
解决:这种情况就可以合理的在初值列中遗漏那些 赋值操作和初始化 一样好的成员变量,把它们用赋值操作,并将这些操作移到一个private函数,供所有构造函数使用,这种做法尤其适用在:成员变量的初值是由文件或数据库读入的时候。当然相对于这种的伪·初始化,还是用成员初值列的 真·初始化 更可取。
2.现象:C++ 有着十分固定的 成员初始化次序,次序就是 base classes 更早于其 derived classes 被初始化。classes成员变量总是以其声明的顺序次序初始化。
解决:当在成员初值列中条列各个成员时,最好总是以其声明次序为次序,这样就避免了一些隐藏的错误,比如初始化数组前要指定数组大小,因此代表大小的变量要先于数组初始化。
3.
①现象:继承与第二个现象,就是——non-local static对象(不同编译单元内定义)的初始化次序
②解释:首先static 对象,它的寿命从被构造出来直到程序结束为止,这样 stack 和 heap-based 对象都被排除,这样的对象包括 global对象,定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。(函数内的static对象成为local static 对象,其他static对象成为 non-local static 对象)
其次 编译单元,这是指 产出单一 single object file(目标文件)的那些源码。基本上就是 单一源码文件加上其所含入的头文件。
而C++ 对 定义于不同编译单元内的non-local static 对象的初始化次序并无明确定义。
③例子:这种例子也很明显,就是 A 中 extern了B,并且调用了B的对象,但是,可能调用A的对象的时候,B并没有初始化,因此会导致错误,因为调用A时,调用了B的对象,但是B没有初始化。
④原因:原因也是非常简单,因为决定这个次序太难,甚至于无解的地步。
⑤解决:将每一个non-local static对象搬到自己的专属函数内(该对象在此函数声明为static)这些函数返回一个reference指向它所含的对象,这样 non-local static 对象被 local static对象替换了。
⑥原理:它的基础是:C++保证,函数内的local static 对象会在”该函数被调用期间“”首次遇上该对象定义式“时被初始化。而且这样做以后,如果在执行期间,没有调用 non-local static 的仿真函数,就绝对不会引发构造和析构成本,这也是原来的non-local static没有的好处。
四 Please Remember
<1>为内置型对象进行手工初始化,因为C++不保证初始化它们。
<2>构造函数最好用 成员初值列 ,而不要在构造函数中使用赋值操作。初值列列出的成员变量,应该按照其在类内的声明次序进行排序。
<3>为免除”跨编译单元之初始化次序“问题,请以local static对象替换 non-local static对象。
第一章结束,
最近一段时间真的好忙啊 +_+..
各种事找上来,Effective C++ 这本书,其实已经看了不少,但是一直没时间更新笔记,
cocos2d-x 的 三消——万圣大作战 也做完了,接下来又要开始新的一个开发,
算法重拾系列,也要更新。。
化压力为动力,继续冲下去吧!
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。