一. 内容
-
读取未初始化的值会导致
不明确的行为
。 -
c++初始化在不同语境表现不同,这些规则很复杂。
-
最佳处理方法就是:
永远在使用对象之前先将它初始化
。对于无任何成员的内置类型,必须手动完成初始化。 -
对于非内置类型,初始化职责落在构造函数,那么规则是:确保每一个构造函数都将对象的每一个成员初始化。
-
注意区分
赋值
和初始化
。C++规定对象的成员变量的初始化动作发生在进入对象的构造函数之前,在构造函数中是赋值,在成员的默认构造函数被自动调用时才是初始化。构造函数一个较佳写法是:使用成员初值列 替换赋值动作。对于未在初始列指定初值的成员,编译器会自动调用默认构造函数(也就是说会先调用默认构造函数,在再执行构造函数内的赋值语句
)。初值列的效率无疑更高。 -
如果变量类型是
const
或者references
,它们一定需要初值,而不是赋值。也就意味着要么在声明式初始化或者在成员初值列初始化。 -
对于多个构造函数,很多变量初始化都是相同的,对于那些赋值表现的像初始化一样好的成员变量,可以改用它们的赋值操作,然后提取到某个函数中,通常是private,供所有的构造函数调用。特别是需要文件或者数据库去指定值的变量。
-
C++有着非常固定的成员初始化次序。
base class 更早于 derived class 被初始化
。class成员总是以声明次序被初始化。即使在成员初值列中定义的次序不同
,也不会有任何影响。所以避免误导,最好总是以声明次序为初值列次序。还有隐晦错误就是,成员变量的初始化具有依赖性,某些变量初始化需要另一些变量先有初始值。 -
最后一个问题,不同编译单元内定义的非局部静态对象的初始化次序。所谓编译单元,是指产出单一目标文件的那些源码:基本上是单一源码文件加上其所含入的头文件。
问题在于:如果某编译单内的某个非局部对象的初始化依赖于另一个编译单元内某个非局部对象,而这个对象可能尚未被初始化,然后就会导致未定义行为。
因为C++对于定义不同编译单元的非局部静态对象的初始化次序并无要求。这是有原因的,决定它们的初始化次序相当困难,甚至无解,或者不值得寻找可决定正确次序的特殊情况。
幸好一个小小的设计可以解决:
将每一个非局部静态对象搬到自己专属函数中,函数返回静态对象的引用
,然后由用户调用这些函数,而不直接涉及这些对象。换句话说非局部静态对象被局部静态对象替换了。如果熟悉设计模式,想必认出来这是单例模式
常见的实现手法。因为C++保证:
函数内的局部静态对象会在该函数第一次被调用时被初始化
。这样保证你获得的引用将指向一个历经初始化的对象。更好的是,如果你不调用这个函数,绝不会引发构造和析构成本。但从多线程来看,这使得系统带有不确定性,一个比较好的做法就是:在程序的单线程启动阶段手工调用所有的单例函数。
如果你的初始化存在对象A初始化依赖对象B,对象B的初始化又依赖于对象A,那什么也救不了,你该避免这种病态的情况。
二. 总结
- 为内置对象进行手工初始化,因为C++不保证初始化它们。
- 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列顺序应该和它们在class中的声明次序相同。
- 为免除 跨编译单元之初始化次序 问题,请以局部静态对象替换非局部静态对象。