联合体中的大括号或等号初始化器

21

相关文章: 如何在联合体中初始化非POD成员

标准规定:

联合体最多只能有一个非静态数据成员具有花括号或等号初始化器。

但是

struct Point {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

union U {
    int z;
    double w;
    Point p = Point(1,2);
};


#include <iostream>
int main () {
    U u;
    std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
}

打印出 4196960:0 而非预期的 1:2

我认为这是编译器的错误。是这样吗?


1
我不会指望这段代码能编译通过,因为由于Point没有一个平凡的默认构造函数,联合体的默认构造函数应该被隐式删除了。当然,除非成员初始化算作用户提供的默认构造函数。 - chris
g++ 打印 134514827:-1218232320 - Veritas
我忘了提到。我的g++安装可重复(这是一个词吗?)打印出那个。 - Sebastian Mach
2
经过一些搜索,这似乎是GCC的一个bug。 - Veritas
2
我猜测clang之所以能够成功编译这个代码,是因为它临时实现了一个扩展解决方案来处理核心工作组问题1623:"由于非静态数据成员初始化器的存在在道义上等同于mem-initializer,当一个联合成员具有非静态数据成员初始化器时,这些规则可能需要修改,不再将生成的构造函数定义为已删除。"尽管Clang中的C++缺陷报告支持没有提到这一点。 - Casey
显示剩余5条评论
1个回答

2
C++11 [class.ctor]/5 规定:
X 的默认构造函数是指可以在不传入参数的情况下调用的类 X 的构造函数。如果类 X 没有用户声明的构造函数,则将自动声明一个没有参数的构造函数作为默认构造函数(8.4)。自动声明的默认构造函数是其类的 inline public 成员。如果类 X 的默认构造函数被定义为已删除,则表示该默认构造函数是默认的,当且仅当以下情况之一成立时:

  • X 是一个类似于联合体的类,其中包含具有非平凡默认构造函数的变量成员。
  • 任何没有使用 大括号或等号初始化器 的非静态数据成员都是引用类型。
  • 任何无默认构造函数的 const 限定类型(或数组)的非变量非联合体非静态数据成员,没有使用 大括号或等号初始化器
  • X 是一个联合体,其所有变量成员都是 const 限定类型(或数组)。
  • X 是一个非联合体类,其中任何匿名联合体成员的所有成员都是 const 限定类型(或数组)。
  • 任何直接或虚拟基类或非静态数据成员,没有使用 大括号或等号初始化器,其类类型为 M(或数组),并且要么 M 没有默认构造函数,要么对于 M 的默认构造函数应用的重载决议(13.3)结果是二义性的,要么是一个从默认的默认构造函数中删除或无法访问的函数。
  • 任何直接或虚拟基类或非静态数据成员都具有析构函数,该析构函数在默认构造函数中被删除或无法访问。

如果默认构造函数不是用户提供的,并且:

  • 它的类没有虚函数(10.3)和虚基类(10.1),
  • 它的类没有任何非静态数据成员使用了 大括号或等号初始化器
  • 它的类的所有直接基类都有平凡的默认构造函数,
  • 对于其类的所有非静态数据成员,如果它们是类类型(或数组),则每个这样的类都有平凡的默认构造函数。

否则,该默认构造函数就是 非平凡的

由于 OP 中的结构体 Point 具有非平凡的默认构造函数,
Point() {}

一个包含类型为Point的成员的联合体的默认构造函数未定义时,根据第一条规则,应该定义为删除状态。
  • X是一个类似于联合体的类,其中包含具有非平凡默认构造函数的变体成员。
这将导致OP中呈现的程序无法正常运行。
然而,在一个联合体的成员具有大括号或等号初始化器的情况下,委员会似乎认为这是一个缺陷,详见core working group issue 1623
根据12.1 [class.ctor]第5段,如果类X的默认构造函数是默认的且存在以下情况之一,则其被定义为已删除:
- X是一个类似于union的类,其中具有非平凡默认构造函数的变体成员, - ... - X是一个union,其所有的变体成员都是const限定类型(或其数组), - X是一个非union类,并且任何匿名union成员的所有成员都是const限定类型(或其数组), - ...
因为非静态数据成员初始化器的存在等同于mem-initializer,所以当union成员具有非静态数据成员初始化器时,这些规则可能需要修改,以便不将生成的构造函数定义为已删除。(注意9.5 [class.union]第2-3段和7.1.6.1 [dcl.type.cv]第2段中的非规范参考文献,如果更改此限制,则还需要更新这些参考文献。)
在9.5 [class.union]中添加一个要求,如果联合的所有成员都具有const限定类型,则需要非静态数据成员初始化器或用户提供的构造函数,这也将很有帮助。
更一般地说,为什么仅因为某个成员具有非平凡的默认构造函数就将默认构造函数定义为已删除?Union本身并不知道哪个成员是活动的,而且默认构造不会初始化任何成员(假设没有brace-or-equal-initializer)。控制活动成员的生命周期(如果有)取决于Union的“所有者”,并且要求用户提供构造函数是强制使用不合理的设计模式。同样,为什么仅因为某个成员具有非平凡的析构函数就将默认析构函数定义为已删除?如果Union还具有用户提供的构造函数,则我将同意此限制。
问题1623的状态为“草案”,表明委员会认为该问题可能是一个缺陷,否则为什么允许一个联合成员有一个“大括号或等号初始化程序”?但是,委员会尚未投入时间确定决议的适当措辞。实际上,在当前的C++14草案N3936([class.ctor]/4)中,该段落基本相同,只是用更简单的“任何潜在构造的子对象”替换了“任何直接或虚基类或非静态数据成员”的措辞。
虽然两个编译器的行为都不完全符合标准,但我认为Clang的行为符合标准的精神。似乎GCC会因删除默认构造函数和“大括号或等号初始化程序”的组合而变得混乱: GCC 应该遵循标准并将程序诊断为不合法,或者模仿 Clang 的行为,并从“花括号或等于初始化器”生成适当的构造函数。

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