Effective C++读书笔记(3)(一)

2014-11-24 12:20:03 · 作者: · 浏览: 2

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

Make sure that objects are initializedbefore they're used.

关于"将对象初始化"这事,C++ 似乎反复无常。在某些语境下内置类型和类的成员变量保证被初始化,但在其他语境中却不保证。

读取未初始化的值会导致不明确的行为。它可能让你的程序终止运行,可能污染了正在进行读取动作的那个对象,可能导致不可测知的程序行为,以及许多令人不愉快的调试过程。

最佳处理办法就是:永远在使用对象之前先将它初始化。无论是对于内置类型、指针还是读取输入流,你必须手工完成此事。

l 为内置型对象进行手工初始化,因为C++不保证初始化它们。

内置类型以外的任何其他东西,初始化则由构造函数完成,确保每一个构造函数都将对象的每一个成员初始化。

这个规则很容易奉行,重要的是别混淆了赋值和初始化。考虑一个用来表现通讯簿的class,其构造函数如下:

1. class PhoneNumber { ... };

2. class ABEntry { //ABEntry = "Address Book Entry"

3. public:

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

5. const std::list& phones);

6. private:

7. std::string theName;

8. std::string theAddress;

9. std::list thePhones;

10. int numTimesConsulted;

11. };

12. ABEntry::ABEntry(const std::string& name, const std::string& address,

13. const std::list& phones)

14. {

15. theName = name; //这些都是赋值(assignments),

16. theAddress = address;//而非初始化(initializations)。

17. thePhones = phones;

18. numTimesConsulted = 0;

19. }

这会导致ABEntry对象带有你期望(你指定)的值,但不是最佳做法。C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在ABEntry构造函数内,theName, theAddress和thePhones都不是被初始化,而是被赋值。初始化的发生时间更早,发生于这些成员的default构造函数被自动调用之时(比进入ABEntry构造函数本体的时间更早)。

使用所谓的member initialization list(成员初值列)替换赋值动作会更好:

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

2. const std::list& phones)

3. :theName(name),

4. theAddress(address), //现在,这些都是初始化(initializations)

5. thePhones(phones),

6. numTimesConsulted(0)

7. { } //现在,构造函数本体不必有任何动作

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

1. ABEntry::ABEntry( )

2. :theName(), //调用theName的default构造函数;

3. theAddress(), //为theAddress做类似动作;

4. thePhones(), //为thePhones做类似动作;

5. numTimesConsulted(0)//记得将numTimesConsulted显式初始化为0

6. { }

请立下一个规则,规定总是在初值列中列出所有成员变量,并总是使用成员初值列。

C++ 有着十分固定的"成员初始化次序",base classes早于其derived classes,而class的成员变量总是以其声明次序被初始化。回头看看ABEntry,其theName成员永远最先被初始化,然后是theAddress,再来是thePhones,最后是numTimesConsulted,即使它们在成员初值列中以不同的次序出现。为避免某些可能存在的晦涩错误(两个成员变量的初始化带有次序性,如初始化array时需要指定大小,因此代表大小的那个成员变量必须先有初值),当你在成员初值列中条列各个成员时,最好总是以其声明次序为次序。

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

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

static对象:函数内的static对象称为localstatic对象,其他static对象称为non-localstatic对象。程序结束时static对象会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。

编译单元(translation unit):产出单一目标文件(single object file)的那些源码,基本上它是单一源码文件加上其所含入的头文件(#include files)。

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

假设你有一个FileSystem class,它让互联网上的文件看起来好像位于本机(local)。由于这个class使世界看起来像个单一文件系统,你可能会产出一个特殊对象,位于global或namespace作用域内,象征单一文件系统:

1. class FileSystem { //来自你的程序库
2. public:
3. ...
4. std::size_t numDisks() const;//众多成员函数之一
5. ...
6. };
7.