在标准中哪里说U::j的默认成员初始化值应该被编译器忽略?

10
考虑以下代码片段:

#include <iostream>
union U{
    U(): i(1) {}
    int i;
    int j = 2;    // this default member initializer is ignored by the compiler
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';
}

该代码输出 (请参见实时示例):

1
1

在标准文档中哪里提到了编译器会忽略成员U::j的默认成员初始化器?

请注意,下面的联合体不能编译,根据[class.union.anon]/4,这是可以的。因此我希望上面的代码片段也无法编译。

请参见在线示例

union U{
    int i = 1;
    int j = 2;
};

为什么它不能编译?您可以添加另一个构造函数,不初始化ij,然后将使用默认成员初始化器。 - 463035818_is_not_a_number
2个回答

7
在标准中,哪里写到了编译器会忽略成员U::j的默认成员初始化器?请参见C++17 CD中[class.base.init]段落9的第9.1个项目。需要注意的是,您的演示具有未定义行为,因为联合体的活动成员是“i”,但您从“j”读取。这在某些编译器中可以作为一种非标准扩展使用,但在ISO C++中不允许。

我相信你指的是[class.base.init]/9,但根据这句话:“...如果一个给定的可能构造的子对象没有被mem-initializer-id指定...”,我不认为它适用于上面的代码片段。我还会说,在这种特殊情况下,代码可能不是UB,因为ij在内存中具有相同的地址并且都具有类型int - Ayrosa
1
@Ayrosa 对于 j,[class.base.init]/9 中的第一个要点不适用,因为“该联合体的任何其他变体成员都没有被 mem-initializer-id 指定”这个条件不满足,所以你会落到第二个要点,即不执行初始化。 - T.C.
@T.C. 我明白你的意思。但第二点说“不执行初始化”,据我所理解,这包括成员i,它被初始化为联合活动成员,正如代码所示。你如何解释这个矛盾? - Ayrosa
1
@Ayrosa “不执行任何初始化”是针对可能构造的子对象,即j。这并不涉及到i - T.C.
@T.C. 我刚刚验证了,一个类型为 int 的成员根据 [special]/5 满足上述条件。你应该得到这个答案的荣誉。谢谢。 - Ayrosa
显示剩余7条评论

3
请注意,您正在声明一个联合对象,其中所有成员共享同一内存区域 - 成员变量转化为相同数据的不同"类型视图"。
因此,由于成员i和j实际上存储在同一内存位置,您对j执行的任何初始化(使用初始化程序)都将被构造函数设置i所覆盖。
只是为了测试,请从构造函数中删除i的初始化:
#include <iostream>
union U{
    U() {}
    int i;
    int j = 2;    // this initializes both i & j
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';   
}

输出结果将是:
2
2

更新: 根据@Ayrosa的评论并且出于好奇,我修改了原始片段以使用函数(而不是常量)来执行一些初始化操作以诱发副作用。
#include <iostream>

int static someStatic()
{
    std::cout << "Initializer was not ignored\n";
    return(2);
}

union U{
    U(): i(1) {}
    int i;
    int j = someStatic();    // this default member initializer is ignored by the compiler
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';
}

结果是:

1
1

意思是编译器实际上忽略了对someStatic()的调用。

我的示例和你的不同之处在于,在我的情况下,我试图“激活”联合体的两个成员,而这是被 [class.union.anon]/4 禁止的,而在你的情况下,你只是“激活”了一个合法的成员 j。但与我的预期相反,编译器忽略了使用默认成员初始化器进行的初始化,这类似于非联合类的情况。在这种情况下,[class.base.init]/10(待续)... - Ayrosa
指定默认成员初始化程序应被忽略。我原本期望在联合体中找到类似的东西。 - Ayrosa
我猜检查的方法是在其他地方调用一些函数来设置标志并返回j的预期初始化值(int j=somestatic();)。由于构造函数将有效地更改联合体的两个成员(并销毁j的初始化值),因此唯一的验证编译器是否忽略初始化程序的方法是这种方式。 - Fernando Marcos

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