由于Init3和Init4它们拥有类似的用户自定义默认构造函数,因此它们的ic1.i和id1.i值相同,应该都是随机值,而ic2.i和id2.i虽然被要求值初始化,但也是随机值。
对于Init5,我们显式的定义了用户自定义默认构造函数,并且使用了构造函数初始化列表来值初始化数据成员。
由于Init5我们为它显式提供了默认构造函数,并且手动的初始化了数据成员,因此它的ie1.i和ie2.i都会被初始化为0。
以上是我们的预测,结果会是这样吗?遗憾的是,结果不一定是这样。是我们哪里出错了?我们并没有错误,上面的程序结果取决于你使用的操作系统、编译器版本(比如gcc-5.0和gcc-7.0)和发行版(比如gcc和clang)。可能有的人能获得和推测完全相同的结果,而有的人不能,比如在经常被批不遵守C++标准的微软VC++编译器(VS 2017,DEBUG模式)下,结果却完全吻合(可能是由于微软开始接纳开源和Linux,逐渐的严格遵守了语言标准),GCC的结果也是完全符合,而广受好评的Clang却部分结果符合。当然,相同的Clang编译器在Mac和Ubuntu下结果甚至都不一致,GCC在某些时候甚至比Clang还人性化的Warning告知使用了未初始化的数据成员。
虽然,上面程序中有一些地方因为操作系统和编译器的原因和我们预期的结果不相同,但也有必然相同的地方,比如最后一个使用了构造函数初始化列表的类的行为就符合预期。还有在合成的默认构造函数之前会先零初始化的地方,必然会初始化为0。
至此,我们已经对C++的初始化方式和规则已经有了一个了然于胸的认识,那就是:由于平台和编译器的差异,以及对语言标准的遵守程度不同,我们决不能依赖于合成的默认构造函数。这也是为什么C++ Primer中多次强调我们不要依赖合成的默认构造函数,也说明了C++ Primer在关于手动分配动态内存那里告诉我们,对于我们自定义的类类型来说,为什么要求值初始化是没有意义的。
C++语言设计的一个基本思想是“自由”,对于某些东西它既给出了具体要求,又留出了发挥空间,而那些未加以明确的地方是属于语言的“灰暗地带”,我们需要小心翼翼的避过。在对象的初始化这里,推荐的做法是将默认构造函数删除,由我们用户自己定义自己的构造函数,并且合理的初始化到每个成员,如果需要保留默认构造函数,一定要对它的行为做到心里有数。