(整理自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. centos 内网ip访问mysql数据库

    参考博文: CentOS 配置mysql允许远程登录 Centos6.5 双网卡配置一个上外网一个接局域网  这个博文仅作参考 公司租用景安的服务器,给景安沟通配置内网. [root@bogon ng ...

  2. Win7 x64安装Paramiko出问题

    今天上午windows下配置paramiko环境时出现问题,随手记录下来.   先说一下我的环境: win7 x64 旗舰版.Python3.5.0.pip8.1.0 pip install para ...

  3. ios 判断,qq,银行卡,手机号,邮编,生日,数字,字符串,护照, email

    http://blog.csdn.net/dyllove98/article/details/8635079 IdentifierValidator.h // //  IdentifierValida ...

  4. pay包注释(二)

    @login_required()def to_register(request):    return render_to_response("pay/register_yeepay.ht ...

  5. 简单cpu处理器

    在135例中有一个简单处理器的程序,稍作修改成自己的风格 //date :2013/8/22 //designer :pengxiaoen //function get a mpc with veri ...

  6. 组件接口(API)设计指南-文件夹

    组件接口(API)设计指南-文件夹 组件接口(API)设计指南[1]-要考虑的问题 组件接口(API)设计指南[2]-类接口(class interface) 组件接口(API)设计指南[3]-托付( ...

  7. iOS swift lazy loading

    Why bother lazy loading and purging pages, you ask? Well, in this example, it won't matter too much ...

  8. HDU 1432 Lining Up (POJ 1118)

    枚举,枚举点 复杂度为n^3. 还能够枚举边的,n*n*log(n). POJ 1118 要推断0退出. #include<cstdio> #include<cstring> ...

  9. Android学习笔记之View(一):LayoutInflater

    使用LayoutInflater加载布局的两种方式: 第一种: LayoutInflater inflater=LayoutInflater.from(context); inflater.infla ...

  10. AV_百度百科

    AV_百度百科 AV(影片门类)