考虑这个类:
#include <iostream>
struct foo {
int a = 42;
int b = bar();
int bar() { return a; }
};
int main(){
foo f;
std::cout << f.a << " " << f.b;
}
它打印出预期的42 42
。标准是否允许在默认成员初始值设定项中调用成员函数?
我希望以下内容是未定义的:
struct broken {
int a = bar();
int b = 42;
int bar() { return b; }
};
不幸的是确实如此编译时没有警告 https://wandbox.org/permlink/oNR1orwdVQthr7F6.
正如您所发现的,这是合法的,但很脆弱并且不推荐。当您为类成员指定默认初始值设定项时,这些只是在类成员初始值设定项列表中使用该值的语法糖。所以,如果我们看看什么时候可以调用成员函数,我们会发现[类.cdtor]/1 https://timsong-cpp.github.io/cppwp/class.cdtor#1 and [类.cdtor]/4 https://timsong-cpp.github.io/cppwp/class.cdtor#4其中指出:
1) 对于具有非平凡构造函数的对象,引用该对象的任何非静态成员或基类在构造函数开始执行之前导致未定义的行为。对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类会导致未定义的行为。
4) 成员函数,包括虚函数([class.virtual]),可以在构造或销毁([class.base.init])期间调用。[...]
emphasis mine
由于构造函数已经开始执行,并且我们可以调用成员函数,因此我们不在 UB 领域。
我们必须考虑的下一件事是构建顺序,因为成员依赖于它。该信息位于[类.base.init]/13 https://timsong-cpp.github.io/cppwp/class.base.init#13
然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样无论 mem 初始化程序的顺序如何)。
因此,成员是按照在类中声明的顺序构造的,这意味着在您引用的第一个示例中a
当它被初始化之后,你就不再处于 UB 地了。
在第二个示例中,您引用了一个尚未初始化的对象,并且读取未初始化对象的值是未定义的行为。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)