结论
调用构造函数即开始初始化流程:初始化列表(委托构造)->缺省值->构造函数体。
其中,初始化列表的初始化顺序和缺省值初始化顺序由声明顺序决定;构造函数体内由代码顺序决定。
例外: 当constexpr static作为缺省值时,该成员优先由缺省值初始化,然后进行列表初始化。
分析
起因和使用场景
尝试通过
Children() :Children(kDefault) {}
复用单参的构造函数代码,出现了初始化顺序的问题。
代码分析
父类Base
,子类Child
。
情况1
考虑以下代码:
class Base {
public:
const int kDefault = 1234;
Base() : member_(kDefault) {} // Constructor (1)
Base(int member) : member_(member) {} // Constructor (2)
int member_;
};
class Child : public Base {
public:
Child() : Base(kDefault) {}
};
尝试用一父类const
成员作为参数,委托构造形式调用父类的构造函数(2)
,此时,根据结论中初始化顺序:
(1) 父类尚未调用构造函数;
(2) 父类的由缺省值构造的const
成员kDefault
尚未被构造;
因此虽然kDefault
是一个const int
成员,其值此时却未被初始化,如果调用委托构造,其构造函数的参数是一个未初始化的值!
情况2
现在考虑另一种方式,如果不用父类的kDefault
,由子类自己定义一个kChildDefault
,再调用父类构造呢?
class Base {
public:
const int kDefault = 1234;
Base() : member_(kDefault) {} // Constructor (1)
Base(int member) : member_(member) {} // Constructor (2)
int member_;
};
class Child : public Base {
public:
const int kChildDefault = 5678;
Child() : Base(kChildDefault) {}
};
在调用子类构造函数时,首先执行初始化列表,此时缺省值还没有初始化,因此传递给父类构造的参数也是一个未初始化的值。
情况3(特殊情况)
class Base {
public:
const int kDefaultA = 100;
const int kDefaultB;
Base() : kDefaultB(kDefaultA / 2) {}
}
当初始化列表中,某缺省值(kDefaultA
)被另一个需要初始化的值(kDefaultB
)依赖时,该缺省值会先被初始化,然后用于初始化依赖其的那个值。此时,两个值都会被正确地初始化。