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

Make sure that  objects are initialized before they're used

通常如果你使用C part of C++(见条款1)而且初始化可能招致运行期成本,那么不保证发生初始化。一旦进入non-C parts of C++,规则有些变化。这就很好地解释了为什么array(来自C part of C++)不保证其内容被初始化,而vector却有此保证。

最佳处理办法就是:永远在使用对象之前先把它初始化。对于无任何成员的内置类型,你必须手动完成此事。至于内置类型以外的任何其他东西,初始化责任落在构造函数身上。确保每一个构造函数都将对象的每一个成员初始化。

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

class PhoneNumber {};
class ABEntry{
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;                //这些都是赋值
	theAddress = address;          //
	thePhones = phones;            //而非初始化
	numTimesConsulted = 0;         //
}

ABEntry::ABEntry(const std::string& name, const std::string& address,
	const std::list<PhoneNumber> &phones)
	:theName(name),
	theAddress = address,       //这些都是初始化
	thePhones = phones,
	numTimesConsulted = 0
{
}

第一个构造函数,theName,theAddress和thePhones都不是初始化,而是被赋值。初始化的发生时间更早,发生于这些成员的default构造函数被自动调用之时(比进入ABEntry构造函数本体的时间更早)。但这对numTimesConsulted不为真,因为它属于内置类型,不保证一定在你所看到的那个赋值动作的时间点之前获得初值。而第二版避免了调用default构造函数,而是直接进行copy构造。对大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的。对于内置型对象,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列表来初始化。

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

C++有着十分固定的“成员初始化次序”。次序总是相同:base classes更早于其derived classes被初始化,而class的成员变量总是以其声明次序被初始化

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

所谓static对象,其寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。这种对象包括global对象、定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static称为local static对象,其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,这就是它们的析构函数会在main()结束时自动调用。

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

现在我们关心的问题涉及至少两个源码文件,每一个内含至少一个non-local static对象。真正的问题是:如果某编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static 对象”的初始化次序并无明确定义。

幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static对象搬到自己的专属函数内,这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了,这时Singleton模式的一个常见实现手法。这个手法的基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

但是这些函数“内含static对象”的事实是它们在多线程系统中带有不确定性。再说一次,任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段手工调用所有reference-returning函数,这可消除与初始化有关的“竞速形势”。

为避免在对象初始化之前过早地使用它们,你需要做三件事。第一,手工初始化内置型non-member对象。第二,使用成员初值列对付对象的所有成分。最后,在“初始化次序不确定性”氛围下加强你的设计。

请记住

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值