现代C++编程实战:132个核心技巧示例(原书第2版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.4.2 工作原理

非静态成员应该用构造函数初始化列表的方式进行初始化,像下面这个示例这样:

然而,许多开发人员并不喜欢使用初始化列表这种方式,他们更倾向于在构造函数体内进行赋值来完成初始化,甚至两者混用。这有几个原因:对于有很多成员的类,在构造函数体内进行赋值初始化比一长串的初始化列表(也许会显示多行)更简洁;他们所熟悉的别的语言根本就没有初始化列表。不幸的是,也有可能他们根本不知道初始化列表。

重要的是要注意,非静态数据成员初始化的顺序是它们在类定义中声明的顺序,而不是它们在构造函数初始化列表中初始化的顺序。另外,非静态数据成员的销毁顺序与构造顺序相反。

通过在构造函数体内进行赋值来实现初始化并不高效,因为这样会导致先创建一个临时对象,然后再销毁该临时对象。如果不用初始化列表进行初始化,非静态成员会通过默认构造函数进行初始化,然后在构造函数体赋值时,调用赋值操作符。这样做效率很低,因为如果默认构造函数分配一个资源(内存或者文件),这个资源将会被析构,然后在调用赋值操作符时被重新分配。请看下面的代码片段:

上面代码的输出结果如下,展示了成员f是如何在构造函数里面先被初始化,然后被重新赋一个值的:

初始化方法从构造函数体内赋值改为初始化列表,会用复制构造函数调用替换默认构造函数调用和赋值操作符:

加了上述代码之后的输出如下:

基于这些原因,至少对于内置类型(例如bool、char、int、float、double或者指针)以外的类型,都应该采用构造函数初始化列表这种形式进行初始化。然而,为了保持初始化风格的一致性,在可能的情况下也应该总是使用构造函数初始化列表进行初始化。在以下几种情况但不仅仅局限于这几种(可以适当扩展),不能用初始化列表进行初始化:

❍ 如果一个成员必须使用包含该成员的对象的引用或者指针来进行初始化,那么有的编译器在初始化列表中使用this指针会发出警告,因为编译器认为在该对象被构造之前使用了this指针。

❍ 如果两个成员必须互相引用。

❍ 如果想测试输入参数,并且想在用参数值初始化非静态数据成员之前抛出异常。

从C++11标准开始,非静态数据成员可以在类声明时初始化。这被称作默认成员初始化,因为它表示应该用默认值初始化。默认成员初始化适用于常量以及那些不基于构造函数参数初始化的成员(换言之,成员的初始化不依赖对象的构造方式):

在前面的例子中,DefaultHeight和DefaultWidth都是常量,因此它们不依赖对象的构造方式,所以它们在声明时初始化。textFlow对象是非常量非静态数据成员,它的值不依赖对象的初始化(它的值可以通过另外的成员函数进行修改),因此也可以在声明的时候用默认成员初始化方法进行初始化。另外,text也是一个非常量非静态数据成员,但是它的初始值依赖对象的构造方式,因此它是通过传给构造函数的参数值以构造函数初始化列表的形式进行初始化的。

如果一个数据成员既可用默认成员初始化方法初始化,也可用构造函数初始化列表形式初始化,则后者优先,默认值会被丢弃。为了说明这一点,我们再看看之前代码中的foo类以及下面的bar类:

输出有所不同,在这个例子中,它的输出如下:

输出不同的原因是默认初始化列表的值被丢弃了,对象并没有被初始化两次。