数组元素可以进行自我引用初始化吗?

22

请考虑以下代码,在此代码中我们根据 D 的另一部分初始化了其一部分:

struct c {
    c() : D{rand(), D[0]} {}
    int D[2];
};

int main() {
    c C;
    assert(C.D[0] == C.D[1]);
}

上面的程序是否定义良好?我们可以安全地使用同一数组的一部分来初始化另一部分吗?


5
我的第一反应是“不要”。我的第二个想法是“初始化列表有顺序保证”。我的第三个想法是“初始化发生在参数评估之后,对吗?” - Yakk - Adam Nevraumont
4个回答

16
当聚合体(包括数组)从大括号列表初始化时,每个聚合元素都从列表的相应元素初始化(“按递增下标或成员顺序”)。虽然我找不到确切的规则说明每个元素初始化在前一个元素之后,但标准中有一个示例清楚地暗示了这是预期的含义。该示例位于[dcl.init.aggr]中:
struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };

使用int{}表达式的值(即0)初始化ss.a1ss.b"asdf",并将ss.d初始化为ss.b[ss.a]的值(即's'),其中ss.c被初始化为一个表达式的值。


3
在大括号初始化列表的初始化器列表内,包括任何由参数包展开(14.5.3)得到的初始化器子句在内,都按照它们出现的顺序进行求值。也就是说,在逗号分隔的初始化器列表中,与给定初始化器子句相关的每个值计算和副作用都会在后面的任何初始化器子句相关的每个值计算和副作用之前被排序执行。 - Casey
2
@Casey:副作用是否包括初始化聚合成员? - Kerrek SB
初始化肯定是一种副作用,而且声称该副作用与给定的初始化器子句相关联也不是不合理的。鉴于没有任何相反的语言,我认为这是预期的解释。 - Casey

11

数组成员可以进行自引用初始化吗?

是的。

struct c {
    int a[3];
    c() : a{4, a[0], 3} {} // a[0] is initialized to 4.
                           // a[1] is initialized to whatever a[0] is. (4)
                           // a[2] is initialized to 3.
};

但是请考虑这个例子:

struct c {
    int a[3];
    c() : a{a[1], 4, a[1]} {} // a[0] is initialized to whatever a[1] is.(Garbage value)
                              // a[1] is initialized to 4.
                              // a[2] is initialized to what a[1] is now (4).
};

这里,数组a中的第一个元素将是a[1]中的任何值, 通常情况下将是垃圾值。第二个元素被初始化为4, 第三个元素被初始化为a[1]中现在的值4

此外,当您未在{}内列出数组中的所有元素时, 未列出的元素将进行默认初始化:

struct c {
    int a[5]; // notice the size
    c() : a{a[1], 2, 3, 4}{}  // a[0] will get value that is in a[1]
                              // but since a[1] has garbage value,
                              // it will be default initialized to 0.
                              // a[1] = 2
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};

然而,列出已初始化的元素将会给你想要的值。
以上面的例子为例:

struct c {
    int a[5];
    c() : a{1, a[0], 3, 4}{}  // a[0] = 1
                              // a[1] = 1
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};

2
根据cppreference.com
聚合初始化的效果如下: 每个数组元素或非静态类成员,按照数组下标/在类定义中出现的顺序,从初始化列表的相应子句进行复制初始化。
你的代码看起来没问题。但是有些令人困惑。

在我看来,这会降低可读性,而且很多人不知道它们是否会按顺序评估。 - masoud

0
不要写 D{rand(),D[0]},因为当构造函数运行时,第一个 rand() 不一定会先执行,然后才是 D[0],这完全取决于编译器,D[0] 可能会先执行,在这种情况下 d[1] 将包含垃圾值。它完全取决于编译器,它可以先编译第二个参数,然后再编译第一个参数,或者反之,执行此语句可能导致未知的行为。

3
标准明确规定在调用 D[0] 之前必须调用 rand(),所以你是错误的:il 不是函数调用,如果它是函数调用,你就是正确的。不太清楚 D[0] 是否在读取之前被初始化。 - Yakk - Adam Nevraumont
1
我目前正在学习C++,我在使用的书籍《C++ Primer》中读到了这种行为。书中明确指出,哪个参数先执行是未知的行为,并且将一个参数依赖于其他参数进行初始化不是一个好的实践。 - Gurleen Sethi
3
这是一个引用吗?或者你为什么要将其放在引用块中?如果是,请适当标明出处(书名、页码、作者、网址等)。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接