(整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

读取未初始化的值会导致不确定的行为。在某些平台上,仅仅只是读取为初始化的值,就有可能让你的程序终止运行。更可能的情况是读入一些“半随机”bits,污染了正在进行读取的那个对象,最终导致不可测知的程序行为,以及令人不愉快的调试过程。

提出最佳准则:永远在使用对象之前将它初始化。分为两部分:对于无任何成员的内置类型,你必须手工完成此事;至于内置类型以外的其他任何东西,初始化责任落在构造函数身上,规则:确保每一个构造函数都将对象的每一个成员初始化。

这个规则很容易奉行,有两点需要注意:

1 赋值和初始化的区别

考虑一个用来表现通讯簿的class,其构造函数如下:

   1: class PhoneNumber{...};

   2: class ABEntry{

   3: public:

   4:     ABEntry ( const string& name , const string& address , 

   5:               const list<PhoneNumber>& Phones ) ;

   6:  

   7: private:

   8:     string theName ;

   9:     string theAddress ;

  10:     list<PhoneNumber> thePhones ;

  11:     int numTimeConsulted ;

  12: };

  13:  

  14: ABEntry::ABEntry( const string& name , const string& address , 

  15:                   const list<PhoneNumber>& Phones ) 

  16: {

  17:     theName = name ;       //这些都是赋值,而非初始化

  18:     theAddress = address ;

  19:     thePhones = phones ;

  20:     numTimeConsulted = 0 ;

  21: }

这会导致ABEntry对象带有你期望的值,但不是最佳做法。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在ABEntry的构造函数内,theName、theAddress和thePhones都不是被初始化,而是被赋值。初始化发生的时间更早,发生于这些成员的default构造函数被自动调用之时(比进入ABEntry的构造函数本体的时间更早)。但这对numTimeConsulted不为真,因为它属于内置类型,不保证一定在你看到的那个赋值动作的时间点之前获得初值。ABEntry的构造函数的一个较佳的写法是,使用所谓的member initialization list(成员初值列)替换赋值动作:

   1: ABEntry::ABEntry ( const string& name , const string& address ,

   2:                    const list<PhoneNumber>& phones )

   3:     :theName(name),       //现在,这些都是初始化

   4:      theAddress(address),

   5:      thePhones(phones),

   6:      numTimeConsulted(0)

   7: {}

这个构造函数和上一个的最终结果相同,但通常效率更高。基于赋值的那个版本首先调用自定义类型成员变量的default的构造函数为它们设初值,然后立刻再对它们赋予新值。default构造函数的一切作为因此浪费了。成员初值列的做法避免了这一问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各个成员变量之构造函数的实参,调用copy构造函数。

对于大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效很多。对于内置类型,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。同样道理,甚至当你想要default构造一个成员变量,你都可以使用成员初值列,只要指定无物作为初始化实参即可。请立下一个规则:规定总是在初值列中列出所有变量,以免还要记住哪些成员变量无需初值。

有些情况下即使成员变量属于内置类型,也一定要使用成员初值列。是的,如果成员变量是const或reference,它们就一定需要初值,不能被赋值。为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总使用成员初值列。这样做有时绝对必要,且又往往比赋值更高效。

许多classes拥有多个构造函数,每个构造函数有自己的成员初值列。如果这种classes存在许许多多的成员变量和/或base classes,多份成员初值列的存在就会导致不受欢迎的重复和无聊的工作。这种情况下可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用赋值操作,并将那些赋值操作移往某个函数(通常private),供所有构造函数调用。这种做法在“成员变量的初值系由文件或数据库读入”时特别有用。

另外,C++有着十分固定的“成员初始化次序”:base class更早于derived class被初始化,而class的成员变量总是以其声明次序被初始化。

2 “不同编译单元内定义之non-local static对象”的初始化次序

我们的问题是:如果某编译单元内的某个non-local static 对象的初始化动作使用了另一编译单元内的某个non-local static 对象,它所使用到的这个对象可能未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确的定义。

书上有个例子,大家可以看看。幸运地是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。

这个手法的基础在于:C++保证,函数内的local static 对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。所以你如果以“函数调用”替换“直接访问non-local static 对象”,你就获得了保证,保证你获得的那个reference将指向一个历经初始化的对象。

请记住:

(1)为内置类型进行手工初始化,因为C++不保证初始化它们。

(2)构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中声明的次序相同。

(3)为免除“跨编译单元之初始化次序”问题,以local static对象替换non-local static对象。

Effective C++_笔记_条款04_确定对象被使用之前已先被初始化的更多相关文章

  1. EC读书笔记系列之2:条款4 确定对象被使用前已先被初始化

    条款4:确定对象被使用前已先被初始化 记住: ★为内置对象进行手工初始化,因为C++不保证初始他们 ★构造函数最好使用初始化列表,而不要在构造函数本体内使用赋值操作.初始化列表列出的成员变量,其排列次 ...

  2. [Effective C++ --009]确定对象被使用前已先被初始化

    在确保对象在使用前已先被初始化这一条款的编码实践中,作者为我们总结了三条经验,它们分别是: ------------------------------------------------------ ...

  3. Effective C++ 之 Item 4:确定对象被使用前已先被初始化

    Effective C++ Chapter 1. 让自己习惯C++ (Accustoming Yourself to C++) Item 4. 确定对象被使用前已先被初始化 (Make sure th ...

  4. Effective C++_笔记_条款12_复制对象时勿忘其每一个成分

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 编译器会在必要时候为我们的classes创建copying函数, ...

  5. Effective C++_笔记_条款02_尽量以const、enum、inline替换#define

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 这个条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#d ...

  6. Effective C++_笔记_条款01_视C++为一个语言联邦

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) C++的各种能力和特性使它成为一个无可匹敌的工具,但也可能引发某 ...

  7. Effective C++阅读笔记_条款2:尽量以const,enum,inline替换#define

    1.#define缺点1 #define NUM 1.2 记号NUM可能没有进入记号表,在调试或者错误信息中,无法知道1.2的含义. 改善:通过const int NUM = 1.2; 2.#dein ...

  8. Effective C++_笔记_条款11_在operator=中处理“自我赋值”

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果: ...

  9. Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: ...

随机推荐

  1. C语言,单链表操作(增删改查)(version 0.1)

    这天要面试,提前把链表操作重新写了一遍.备份一下,以备不时之需. 希望有人能看到这篇代码,并指正. // File Name : list.h #include "stdafx.h" ...

  2. POJ 3675 Telescope 简单多边形和圆的面积交

    这道题得控制好精度,不然会贡献WA  QAQ 还是那个规则: int sgn(double x){ if(x > eps) return 1; else if(x < - eps) ret ...

  3. PHP - 多文件上传

    <html> <head> <meta charset="utf-8"> <title>index_uploads</titl ...

  4. 合并k个已排序的链表 分类: leetcode 算法 2015-07-09 17:43 3人阅读 评论(0) 收藏

    最先想到的是把两个linked lists 合并成一个. 这样从第一个开始一个一个吞并,直到所有list都被合并. class ListNode:# Definition for singly-lin ...

  5. CentOS 如何安装git server + Gitolite 【配置不成功需要再测试2015-8-20】

    安装git 关于安装git  可以参考 http://gitolite.com/gitolite/install.html 里面有官方的介绍 1. Git 的工作需要调用 curl,zlib,open ...

  6. 关于php判断中文字符的问题

    在网上找了好多例子,还是这个靠谱点: UTF-8匹配: 在javascript中,要判断字符串是中文是很简单的.比如: var str = "php编程"; if (/^[\u4e ...

  7. Android 播放声音

    public static void PlayAlarmRing(Context mContext) { Uri alert = RingtoneManager.getDefaultUri(Ringt ...

  8. haroxy hdr

    ACL derivatives :ACL的衍生物 hdr([<name>[,<occ>]]) : exact string match 字符串精确匹配 hdr_beg([< ...

  9. mysql 如何修改、添加、删除表主键

    在我们使用mysql的时候,有时会遇到须要更改或者删除mysql的主键,我们能够简单的使用alter table table_name drop primary key;来完成.以下我使用数据表tab ...

  10. 最小生成树(MST)[简述][模板]

    Prim(添点法) 1. 任选一点(一般选1), 作为切入点,设其与最小生成树的距离为0(实际上就是选一个点,将此树实体化),. 2. 在所有未选择的点中选出与最小生成树距离最短的, 累计其距离, 并 ...