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. C# 毕业证书打印《三》

    打印很关键的方法,打印方法DataPrint(),将你要打印的数据信息发送到打印机就可以了,打印机将自动处理. public void DataPrint() { try { PrintDocumen ...

  2. TCP的几个状态

    转自: TCP的几个状态 (SYN, FIN, ACK, PSH, RST, URG)     http://www.cnblogs.com/lidabo/p/5713569.html

  3. js弹出提示信息,然后跳转到另一页面

    <script language="javascript">  alert("您的用户名与密码已成功修改!");  document.locatio ...

  4. jq获取元素

    <tr><td><div id="add"></div></td></tr>$("#add&quo ...

  5. 【leetcode】Populating Next Right Pointers in Each Node II

    Populating Next Right Pointers in Each Node II Follow up for problem "Populating Next Right Poi ...

  6. POJ 2676

    http://poj.org/problem?id=2676 深搜的题目. 题意呢就是一个数独的游戏,应该都知道规则. 思路:我的思路很简单,就是用数组来判断某个数字是否可以使用,而每一个数字都由三个 ...

  7. [Linux]非外网环境下配置lnmp心得

    1.安装gcc编译器 基本所有编译安装包都需要gcc编译器,而且编译安装gcc也需要一个编译器,这是一个悖论.所以,这里只能寻求rpm包安装.但是rpm包需要匹配特定的操作系统内核,所以没有一个通用的 ...

  8. Qt 改变图片大小

    void Setting_TabProduct::changeImageSize(int width,int height,QString imgFile) { QPixmap pixmap(imgF ...

  9. Es6 学习笔记

    变量 let let用来声明变量,作用和var类似,所声明的变量只在let生命的代码块内有效. //1.不允许重复声明 let num = 2; let num = 3; //error //2.块级 ...

  10. nyoj739_笨蛋难题四

    笨蛋难题四 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 这些日子笨蛋一直研究股票,经过调研,终于发现xxx公司股票规律,更可喜的是 笨蛋推算出这家公司每天的股价, ...