(整理自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. ARM异常---一个DataAbort的触发过程:

    一个DataAbort异常的触发过程://////////////////////////////xxxx.inc_STACK_BASEADDRESS EQU 0x33ff8000_MMUTT_STA ...

  2. 利用VC助手(VA)添加注释

    利用VC助手(VA)添加注释 今天想给自己写的代码加上版权信息,同时整理一下代码的注释.但是为了保持同样的格式,总是copy,显得有些繁琐.然后试图找解决方案.我用的是VS 2010, 刚开始是尝试了 ...

  3. BZOJ 1738: [Usaco2005 mar]Ombrophobic Bovines 发抖的牛( floyd + 二分答案 + 最大流 )

    一道水题WA了这么多次真是.... 统考终于完 ( 挂 ) 了...可以好好写题了... 先floyd跑出各个点的最短路 , 然后二分答案 m , 再建图. 每个 farm 拆成一个 cow 点和一个 ...

  4. DDL\DML\DCL\DQL

    [DML] DML = Data Manipulation Language,数据操纵语言,命令使用户能够查询数据库以及操作已有数据库中的数据的计算机语言.具体是指是UPDATE更新.INSERT插入 ...

  5. POJ 2449 求第K短路

    第一道第K短路的题目 QAQ 拿裸的DIJKSTRA + 不断扩展的A* 给2000MS过了 题意:大意是 有N个station 要求从s点到t点 的第k短路 (不过我看题意说的好像是从t到s 可能是 ...

  6. iOS中NSString转换成HEX(十六进制)-NSData转换成int

    http://www.2cto.com/kf/201402/281501.html 1 2 3 4 5 6 NSString *str = @"0xff055008"; //先以1 ...

  7. ASP.NET MVC进阶之路:深入理解Controller激活机制并使用Ioc容器创建对象

    本文标题说是"深入理解Controller"其实有点“标题党”的味道了.本篇只会探讨"Controller"的激活机制,也就是如何创建Controller的并调 ...

  8. Android有效解决加载大图片时内存溢出的问题

    首先,您需要了解一下,图片占用内存的计算方法,传送门:http://blog.csdn.net/scry5566/article/details/11568751 尽量不要使用setImageBitm ...

  9. stm32之ADC

    将模拟量转换为数字量的过程称为模式(A/D)转换,完成这一转换的期间成为模数转换器(简称ADC);将数字量转换为模拟量的过程为数模(D/A)转换,完成这一转换的器件称为数模转换器(简称DAC). 模拟 ...

  10. H5前端面试题及答案(2)

    最近想着跳槽,但面试的邀约不多,内心有点烦躁.梳理梳理心情,跳槽季竞争也大,努力做好自己... 21.请设计一套方案,用于确保页面中js加载完全. <!doctype html> < ...