联合中的大括号或相等初始化器
Posted
技术标签:
【中文标题】联合中的大括号或相等初始化器【英文标题】:brace-or-equal-Initializer in unions 【发布时间】:2014-04-11 06:38:58 【问题描述】:相关:How to initialize a non-POD member in Union
标准说
联合的最多一个非静态数据成员可以有一个大括号或等号初始化器。
但是
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
。
我认为这是一个编译器错误。是这样吗?
【问题讨论】:
我不希望它编译,因为联合的默认构造函数应该被隐式删除,因为Point
没有一个微不足道的默认构造函数。当然,除非该成员初始化算作用户提供的默认构造函数。
g++ 打印 134514827:-1218232320
我忘了说。我的 g++ 安装可重现(这是一个词吗?)打印出来。
经过一番搜索,这似乎是一个 GCC 错误。
我猜想 clang 正在成功编译它,因为它临时实现了 Core Working Group issue 1623 的解决方案作为扩展:“因为非静态数据成员初始化程序的存在在道德上等同于 mem-初始化器,这些规则可能应该被修改,以便在联合成员具有非静态数据成员初始化器时将生成的构造函数定义为已删除。”虽然C++ Defect Report Support in Clang 对此只字未提。
【参考方案1】:
C++11 [class.ctor]/5 个状态:
类
X
的默认 构造函数是类X
的构造函数,可以不带参数调用。如果类X
没有用户声明的构造函数,则没有参数的构造函数被隐式声明为默认(8.4)。隐式声明的默认构造函数是其类的inline public
成员。类X
的默认默认构造函数在以下情况下定义为已删除:X
是一个类似联合的类,它有一个带有非平凡默认构造函数的变体成员, 任何没有 brace-or-equal-initializer 的非静态数据成员都是引用类型, 任何没有 brace-or-equal-initializer 的 const 限定类型(或其数组)的非变体非静态数据成员都没有用户提供的默认构造函数,李>X
是一个联合,它的所有变体成员都是 const 限定类型(或其数组),X
是一个非联合类,任何匿名联合成员的所有成员都是 const 限定类型(或其数组), 任何直接或虚拟基类,或没有 brace-or-equal-initializer 的非静态数据成员,具有类类型M
(或其数组),并且M
具有没有应用于M
的默认构造函数的默认构造函数或重载决议 (13.3) 会导致歧义或导致从默认默认构造函数中删除或无法访问的函数,或者 任何直接或虚拟基类或非静态数据成员都具有一个带有析构函数的类型,该析构函数已被删除或无法从默认的默认构造函数中访问。如果默认构造函数不是用户提供的并且如果:
它的类没有虚函数 (10.3) 和虚基类 (10.1),并且 其类的非静态数据成员没有brace-or-equal-initializer,并且 其类的所有直接基类都有微不足道的默认构造函数,并且 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的默认构造函数。否则,默认构造函数是非平凡的。
由于 OP 中的结构 Point
有一个重要的默认构造函数,
Point()
包含Point
类型成员的联合的默认默认构造函数应根据第一个项目符号定义为删除:
X
是一个类似联合的类,它有一个带有非平凡默认构造函数的变体成员
导致 OP 中呈现的程序格式错误。
然而,委员会似乎认为这是一个缺陷,因为工会成员有一个 brace-or-equal-initializer,per core working group issue 1623:
根据 12.1 [class.ctor] 第 5 段,
如果满足以下条件,则将类 X 的默认默认构造函数定义为已删除:
X
是一个类似联合的类,它有一个带有非平凡默认构造函数的变体成员,...
X
是一个联合体,它的所有变体成员都是 const 限定类型(或其数组),
X
是一个非联合类,任何匿名联合成员的所有成员都是 const 限定类型(或其数组),...
因为非静态数据成员初始化器的存在在道德上等同于 mem-initializer,所以可能应该修改这些规则,不要将生成的构造函数定义为当联合成员具有一个非静态数据成员初始化器。 (注意 9.5 [class.union] 第 2-3 段和 7.1.6.1 [dcl.type.cv] 第 2 段中的非规范性引用,如果更改此限制,也需要更新。)
如果联合的所有成员都具有 const 限定类型,则向 9.5 [class.union] 添加要求非静态数据成员初始化程序或用户提供的构造函数也会很有帮助。
在更一般的说明中,为什么仅仅因为成员具有非平凡的默认构造函数而将默认构造函数定义为已删除?联合本身不知道哪个成员是活动成员,并且默认构造不会初始化任何成员(假设没有 brace-or-equal-initializer)。由联合的“所有者”来控制活动成员(如果有的话)的生命周期,并且需要用户提供的构造函数会强制使用没有意义的设计模式。同样,为什么仅仅因为一个成员有一个非平凡的析构函数就将默认析构函数定义为删除?如果它仅适用于联合还具有用户提供的构造函数时,我会同意此限制。
问题 1623 的状态为“正在起草”,表明委员会认为该问题可能是一个缺陷 - 为什么要允许工会成员使用 brace-or-equal-initializer? - 但尚未花时间确定决议的适当措辞。实际上,该段落在当前的 C++14 草案 N3936 ([class.ctor]/4) 中基本相同,除了“任何直接或虚拟基类或非静态数据成员”的措辞处处被替换为更简单的“任何可能构造的子对象”。
虽然两个编译器的行为并不严格符合,但我认为 Clang 的行为符合标准的精神。似乎 GCC 被删除的默认构造函数和 brace-or-equal-initializer 的组合弄糊涂了:
diagnose the program as ill-formed in the absence of the brace-or-equal-initializer,在存在 brace-or-equal-initializer 和最大警告的情况下,GCC 4.8.2 根本不执行联合初始化,even warns that the members are used uninitialized:
main.cpp: In function 'int main()':
main.cpp:17:39: warning: 'u.U::p.Point::y_' is used uninitialized in this function [-Wuninitialized]
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
^
main.cpp:17:22: warning: 'u.U::p.Point::x_' is used uninitialized in this function [-Wuninitialized]
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
^
GCC 应该要么符合标准并将程序诊断为格式错误,要么模拟 clang 的行为并从 brace-or-equal-initializer 生成适当的构造函数。
【讨论】:
以上是关于联合中的大括号或相等初始化器的主要内容,如果未能解决你的问题,请参考以下文章