详细解读《Effective C++》条款04 :确定对象被使用前先被初始化

1、为什么要确定对象被使用前先被初始化?

在不同的语境中,同一个对象,可能被编译器给默认初始化,可能编译器没有执行默认初始化。使用没有被初始化的对象就可能导致“不确定行为”。

我们虽然有关于什么时候对象被编译器初始化,什么时候不被编译器初始化的规则,但是这些规则比较复杂,记忆起来比较困难。

这些规则总结起来就是:c part of c++ 部分,如果初始化的话可能招致运行期成本,因此不保证初始化。而non-C parts of C++部分一般会保证对象被初始化。但是规则不是一层不变的,总之规则是比较复杂的。

我们既不想记忆这些规则,又不想导致“不确定行为”,那么处理这件事最好的方法就是:永远在使用对象之前将它初始化。

2、两类变量的初始化

(1)(没有成员的)内置类型
必须手动进行初始化。
(2)内置类型以外的类型(类类型)
使用构造函数进行初始化。

3、搞清楚类类型的初始化与赋值的区别

C++规定,成员变量的初始化动作发生在进入构造函数之前。在构造函数中的进行的是赋值操作而不是初始化操作。

4、类类型应该使用初始化列表初始化的原因

如果没有使用列表初始化进行对类类型成员初始化,而是在构造函数内部对类类型成员进行初始化的话,它其实等价于先进行默认初始化,然后在对类类型成员进行copy赋值。而默认初始化的工作相当于是白做的。而使用初始值列表初始化的话,相当于直接copy初始化。不出先使用默认构造函数初始化。这就是其效率差别的原因。

5、内置类型应该使用初始化列表初始化的原因

对于类类型而言,使用由于上述差别,出于效率考虑,我们一般应该使用初始化列表对类类型的成员进行初始化。对于内置类型来说,在效率上,使用初始值列表初始化或者在函数体内copy赋值之间没有差别。但是有时即使将他们进行初始值列表初始化,并不是基于效率的考虑,而是基于正确性的考虑。当成员变量是const的内置类型,或者为引用类型的话,那么必须在定义时初始化,否则就是错误的。

综合上述,我们最好使用初始值列表对无论是类类型还是内置类型进行初始化,而不是在函数体内copy赋值。

6、上述规则的例外

有些时候一个类拥有许多个构造函数,而且拥有很多个基类。这时,使用初始化列表对他们进行初始化的话,对程序员来说,就显得很啰嗦无聊。这时可以遗漏在效率上和初始值列表进行初始化一样的的成员变量,改用copy赋值。
例如:成员变量的初始值列表由文件或者数据库读入时,这种做法比较有用。

7、和初始化相关的另外一个小问题:初始化顺序

C++的成员初始化顺序非常固定:

  • 派生类多包涵的基类对象先初始化
  • 派生类对象所特有的成员变量按照定义顺序初始化

在这里需要注意一个问题,初始化顺序与初始值列表中的顺序与无关,只与类中变量定义的顺序相关。因此,为了避免产问题,我们最好让初始值列表顺序和类中定义的变量顺序一致。

8、关于初始化的又一个问题:不同编译单元内定义“non-local static对象”的初始化顺序为题

static对象是指定义于namespace作用域内的对象、在class内、函数内、以及在life作用域内被声明为static的对象。而函数内的static对象被称为local static对象,其它的就被称为non-local static对象。我们要讨论的就是这些对象的初始化问题。

编译单元指的是:单一的目标文件的源码,加上所包含的头文件的源码,所构成的目标文件。

现在的问题就出在:C++对定义在不同编译单元内的non-local static 对象的初始化顺序并无明确定义。

这样的话,假如文件1中某个non-local static对象y要使用文件2中某个non-local static对象x进行初始化,而编译器并不能x在y之前被初始化,这样就会导致问题出现。

解决这个问题的方法Singleton设计模式
具体做法:
将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static 对象,被local static对象替换了。因为C++保证,函数内的local static对象会在“该函数调用期间”“首次遇上该对象之定义式”时被初始化。

所以函数调用(返回一个reference 指向 local static对象)替换直接访问"non-local static 对象",就保证了你所获得的reference指向了一个初始化后的对象。更好的是,如果没有调用这样的函数,就不会引发构造和析构函数,这是使用non-local static 对象这样的对象所不具备的。

保证这种方法成功的一个前提是:两个对象之间有着合理的初始化顺序。如果A依赖B,而反过来B又依赖于A。那么这种方法就不成立了。

9、这种特殊函数在多线程系统中产生不确定性的解决方法

任何一种non-static对象,无论是否为local或non-local,在多线程系统中等待某件事发生都会遇到麻烦。

解决这种麻烦的方法:在程序的单线程启动阶段手工调用这样的函数,可消除与初始化有关的竞速形式。

posted @ 2019-12-05 22:30  江南又一春  阅读(220)  评论(0编辑  收藏  举报