12

这是关于此的第 n 个问题,但我找不到完全相同的重复项...

假设以下代码:

#include <iostream>

struct S {
    int x;
    int y;
};

class C {
public:
    S s;
    C() : s{123, s.x} {}
};

int main() {
     std::cout << C().s.y << '\n';
}

这样初始化s.y可以吗?(只有 JetBrains 的 ReSharper 通过以下方式抱怨它:)Object member this->s.x might not be initialized

如果有人用标准的引用来确认他们的答案,那就太好了。

4

2 回答 2

1

虽然似乎没有规则明确指出这个技巧是不正确的,但它没有明确定义的行为是不够的。

我认为它在评估顺序方面存在一些问题:

该规则定义了括号列表中表达式的计算顺序;当然,成员初始化也是有顺序的。

可以肯定地说,每个 struct 成员在括号列表中的相应表达式求值之后被初始化(显然s.x在括号列表中是在初始化之前求值s.y)。

但是,似乎没有规则可以说明s.x在您的情况下必须在评估大括号列表的第二个元素之前进行初始化,例如,程序可以在开始初始化结构字段之前评估括号列表中的所有表达式。

当然,不存在规则并不容易证明,但如果不存在,它看起来像 UB。

UPD@PaulFloyd 答案中的规则确实与我的答案中缺少的非常相似,也许它毕竟不是 UB。

于 2017-04-05T15:32:58.027 回答
1

从 C++14

8.5.1 聚合 [dcl.init.aggr]

1 聚合是一个数组或类(第 9 条),没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第 11 条),没有基类(第 10 条),也没有虚函数( 10.3)。

2 当聚合由初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。

这意味着sx首先用123初始化,然后sy用sx初始化

没有优化,GCC 6.3 生成

C::C():
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8] # read address of s
        mov     DWORD PTR [rax], 123   # write 123 to s.x (offset 0 from s)
        mov     rax, QWORD PTR [rbp-8] # read address of s again
        mov     edx, DWORD PTR [rax]   # read contents of s.x to edx
        mov     rax, QWORD PTR [rbp-8] # read address of s
        mov     DWORD PTR [rax+4], edx # write s.y (offset 4 from s)
        nop
        pop     rbp
        ret

这与标准所说的一致。

于 2017-04-05T15:15:48.797 回答