Effective C++

Chapter 1. 让自己习惯C++ (Accustoming Yourself to C++)

Item 4. 确定对象被使用前已先被初始化

(Make sure that objects are initialized before they're used.)

通常如果你使用 C part of C++ 而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入 non-C part of C++, 规则有些变化。这就很好地解释了为什么 array (来自 C part of C++)不保证其内容被初始化,而 vector (来自 non-C part of C++)却有次保证。表面上这似乎是一个无法决定的状态,而最佳的处理办法就是:永远在使用对象之前先将它初始化

1. 对于无任何成员的内置类型,必须手工完成此事。例如:

int x = 0;                                 // 对 int 进行手工初始化
const char* text = "A C-style string"; //对指针进行手工初始化
double d;
std::cin >> d; //以读取 input stream 的方式完成初始化

2. 对于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。这个规则很容易奉行,重要的是别混淆了赋值(assignment)和初始化(initialization)。考虑一个用来表现通讯簿的 class,其构造函数如下:

class PhoneNumber { ... };
class ABEntry //ABEntry = "Address Book Entry"
{
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; //这些都是赋值(assignment),
theAdress = address; //而不是初始化(initialization)。
thePhones = phones;
numTimesConsulted = 0;
}

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

ABEntry 构造函数的一个较佳的写法是,使用所谓的 member initialization list (成员初值列)替换赋值动作

ABEntry::ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
: theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) //现在,这些都是初始化(initialization)
{
//现在,构造函数本体不必有任何动作
}

这个构造函数和上一个的最终结果相同,但通常效率较高。基于赋值的那个版本首先调用 default 构造函数为 theName, theAddress 和 thePhones 设初值,然后立刻再对它们赋予新值,default 构造函数的一切作为因此浪费了。成员初值列(member initialization list)的做法避免了这一问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各成员变量之构造函数的实参。本例中的 theName 以 name 为初值进行 copy 构造,theAddress 以 address 为初值进行 copy 构造,thePhones 以 phones 为初值进行 copy 构造。

对于大多数类型而言,比起先调用 default 构造函数然后再调用 copy assignment 操作符,单只调用一次 copy 构造函数是比较高效的,有时甚至高效的多。对于内置型对象如 numTimesConsulted,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。同样道理,甚至当你想要 default 构造一个成员变量,都可以使用成员初值列,只要指定无物作为初始化实参即可。假设 ABEntry 有一个无参数构造函数,可将它实现如下:

ABEntry::ABEntry( ) : theName(), theAddress(), thePhones(), numTimesConsulted(0)
{
//调用 theName, theAddress, thePhones 的default 构造函数
//记得将 numTimesConsulted 显示初始化为 0
}

记住总是在初值列中列出所有的成员变量,以免还得记住哪些成员变量(如果它们在初值列中被遗漏的话)可以无需初值。

有些情况下即使面对的成员变量属于内置类型(那么其初始化与赋值的成本相同),也一定得使用初值列。如果成员变量是 const 或 references,它们就一定需要初值,不能被赋值(见 Item 5)。为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。

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

C++有着十分固定的“成员初始化次序”。是的,次序总是相同:base classes 更早于其 derived classes 被初始化(见 Item 12),而 class 的成员变量总是以其声明次序被初始化。回头看看 ABEntry,其 theName 成员永远最先被初始化,然后是 theAddress, 再来是 thePhones,最后是 numTimesConsulted。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的),也不会有任何影响。因而在成员初值列中列各个成员时,最好以其声明次序为次序。<通俗来讲,两个成员变量的初始化带有次序性,例如初始化 array 时需要指定大小,因此代表大小的那个成员变量必须先有初值。>

一旦已经很小心地将“内置型成员变量”明确地加以初始化,而且也确保构造函数运用“成员初值列”初始化 base classes 和成员变量,那就只剩唯一一件事情需要操心,就是“不同编译单元内定义的 non-local static 对象”的初值化次序。

具体略。

请记住:

  • 为内置型对象进行手工初始化,因为 C++ 不保证初始化它们。
  • 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。
  • 为免除“跨编译单元的初始化次序”问题,请以 local static 对象替换 non-local static 对象。

Effective C++ 之 Item 4:确定对象被使用前已先被初始化的更多相关文章

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

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

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

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

  3. Effective C++(4) 确定对象被使用前已先被初始化

    危害:读取未初始化的值会导致不明确甚至是半随机化的行为. 最佳处理办法:永远在使用对象之前先将它初始化:确保每一个构造函数都将对象的每一个成员初始化. 1 注意区分赋值和初始化: 从初始化的角度而言, ...

  4. Effective C++学习笔记 条款04:确定对象被使用前已先被初始化

    一.为内置类型对象进行手工初始化,因为C++不保证初始化它们. 二.对象初始化数据成员是在进入构造函数用户编写代码前完成,要想对数据成员指定初始化值,那就必须使用初始化列表. class A { pu ...

  5. Effective C++ 条款04:确定对象被使用前已经先被初始化

    规则一 永远在使用对象之前将它初始化 int x = 0; const char* text = "A C-style string"; double d; std:: cin & ...

  6. EffectiveC++条款04:确定对象被使用前已先被初始化

    不要混淆赋值和初始化,对于大多数类型而言,比起先调用默认构造函数然后调用赋值操作符,只调用一次拷贝构造函数是高效的 对于内置类型,也需要成员初值列(member initialization list ...

  7. [effictive c++] 条款04 确定对象被使用前已被初始化

    成员初始化 在c和c++ 中,使用为初始化的类型经常会引发不可预料的错误,从而使得我们要花费巨大的时间用于调试查找问题,所以确定对象被使用前已被初始化是个非常好的习惯. 永远在使用之前对对象进行初始化 ...

  8. 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化

    C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...

  9. 条款4:确定对象被使用前已被初始化(Make sure that objects are initialized before they're used)

    其实 无论学何种语言 ,还是觉得要养成先声明后使用,先初始化再使用. 1.永远在使用对象之前先将其初始化. 内置类型: 必须手工完成. 内置类型以外的:使用构造函数完成.确保每一个构造函数都将对象的一 ...

随机推荐

  1. 通过分析 JDK 源代码研究 TreeMap 红黑树算法实现

    本文转载自http://www.ibm.com/developerworks/cn/java/j-lo-tree/ 目录: TreeSet 和 TreeMap 的关系 TreeMap 的添加节点 Tr ...

  2. Factor Combinations

    Factor Combinations Problem: Numbers can be regarded as product of its factors. For example, 8 = 2 x ...

  3. 在asp.net mvc中上传大文件

    在asp.net mvc 页面里上传大文件到服务器端,需要如下步骤: 1. 在Control类里添加get 和 post 方法 // get method public ActionResult Up ...

  4. struts2 配置 struts.xml 提示

    1.这个提示通常是在 连网络的时候才可以看到 2.当没有网路的时候我们该如何配置呢? window -->preferences -->xml catelog -->user.... ...

  5. python模块介绍- collections(5)-OrderedDict 有序字典

    1.3.5 OrderedDict 有序字典 OrderedDict是dict的子类,它记住了内容添加的顺序. import collections print 'Regular dictionary ...

  6. printf 的场宽

    这个经常忘记,从百度直到上搜到的,做个记录. 可以在"%"和字母之间的数字表示最大场宽.例如: %3d 表示输出3位整型数, 不够3位右对齐.%9.2f 表示输出场宽为9的浮点数, ...

  7. 14. javacript高级程序设计-表单

    1. 表单脚本 1.1 基础知识 <from>元素表示表单: l acceptCharset:服务器能处理的字符集 l action:接受请求的URL l elements:表单中所有控件 ...

  8. ffmpeg-20160325-snapshot-static-bin

    ffmpeg-20160325-snapshot-static.7z ./configure \ --enable-static \ --disable-shared \ --enable-gpl \ ...

  9. 一个servlet处理来自多个不同页面的请求!

    例如有一个用户表,我们要处理 添加,删除 用户以及登录功能 对应的有add.jsp del.jsp login.jsp等 <body> <!-- login.jsp --> & ...

  10. 如何让数据库在每天的某一个时刻自动执行某一个存储过程或者某一个sql语句

    这就要涉及到代理的知识了哦,首先我们要启动代理服务.