3

考虑这个类:

#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; }
};

不幸的是,它编译时没有警告

4

2 回答 2

3

如您所见,这是合法的,但很脆弱,不推荐。当您为类成员指定默认初始化器时,它们只是在类成员初始化器列表中使用此值的语法糖。因此,如果我们查看何时可以调用成员函数,我们会发现[class.cdtor]/1[class.cdtor]/4指出:

1) 对于具有非平凡构造函数的对象,在构造函数开始执行之前引用对象的任何非静态成员或基类会导致未定义的行为。对于具有非平凡析构函数的对象,在析构函数完成执行后引用对象的任何非静态成员或基类会导致未定义的行为。

4)成员函数,包括虚函数([class.virtual]),可以在构造或销毁([class.base.init])期间调用。[...]

强调我的

由于构造函数已经开始执行,并且我们被允许调用成员函数,我们不在 UB 领域。

接下来我们要考虑的是构造顺序,因为成员依赖于它。该信息在[class.base.init]/13中

然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不管 mem-initializers 的顺序)。

因此,成员是按照它们在类中声明的顺序构造的,这意味着在您的第一个示例中,您a在初始化之后引用它,因此您不在 UB 领域。

在第二个示例中,您指的是尚未初始化的对象,并且读取未初始化对象的值是未定义的行为。

于 2019-11-20T16:21:15.347 回答
1

标准在这里说:

可以为正在构建的对象调用成员函数(包括虚拟成员函数,[class.virtual])。类似地,正在构建的对象可以是 typeid 运算符 ([expr.typeid]) 或 dynamic_cast ([expr.dynamic.cast]) 的操作数。但是,如果这些操作在基类的所有 mem-initializer 完成之前在 ctor-initializer(或在从 ctor-initializer 直接或间接调用的函数中)中执行,则程序具有未定义的行为。

由于您没有基类,因此broken即使它正在构建中,也可以调用成员函数。a将被初始化为不确定的值。

我有点为时过早。正如在另一个答案中看到的那样,存在一个问题,即函数从未定义行为的未初始化值中读取。所以不是这个函数本身的调用,而是它所做的是UB。

于 2019-11-20T16:19:44.293 回答