21

相关:如何在 Union 中初始化非 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.

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

4

1 回答 1

2

C++11 [class.ctor]/5 状态:

类的默认构造函数是可以在没有参数的情况下调用X的类的构造函数。X如果 class 没有用户声明X的构造函数,则没有参数的构造函数被隐式声明为默认值 (8.4)。隐式声明的默认构造函数是inline public其类的成员。类的默认默认构造函数在以下情况下X定义为已删除:

  • X是一个类似联合的类,它有一个带有非平凡默认构造函数的变体成员,
  • 任何没有大括号或等号初始化器的非静态数据成员都是引用类型,
  • 任何没有大括号或相等初始化器的 const 限定类型(或其数组)的非变体非静态数据成员都没有用户提供的默认构造函数,
  • X是一个联合,它的所有变体成员都是 const 限定类型(或其数组),
  • X是一个非联合类,并且任何匿名联合成员的所有成员都是 const 限定类型(或其数组),
  • 任何直接或虚拟基类,或没有大括号或相等初始化器的非静态数据成员,具有类类型M(或其数组),并且M没有应用于M默认构造函数的默认构造函数或重载决议(13.3)导致歧义或导致从默认的默认构造函数中删除或无法访问的函数,或
  • 任何直接或虚拟基类或非静态数据成员都具有带有析构函数的类型,该析构函数已从默认的默认构造函数中删除或不可访问。

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

  • 它的类没有虚函数 (10.3) 和虚基类 (10.1),并且
  • 其类的非静态数据成员没有大括号或相等初始化器,并且
  • 其类的所有直接基类都有微不足道的默认构造函数,并且
  • 对于其类的所有属于类类型(或其数组)的非静态数据成员,每个这样的类都有一个简单的默认构造函数。

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

由于PointOP 中的结构有一个重要的默认构造函数,

Point() {}

包含类型成员的联合的默认默认构造函数Point 根据第一个项目符号定义为已删除:

  • X是一个类似联合的类,它有一个带有非平凡默认构造函数的变体成员

导致 OP 中呈现的程序格式错误。

但是,根据核心工作组问题 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] 添加要求非静态数据成员初始化程序或用户提供的构造函数也会很有帮助。

在更一般的说明中,为什么将默认构造函数定义为删除只是因为成员具有非平凡的默认构造函数?联合本身不知道哪个成员是活动成员,并且默认构造不会初始化任何成员(假设没有大括号或相等初始化器)。由联合的“所有者”来控制活动成员(如果有的话)的生命周期,并且需要用户提供的构造函数会强制使用没有意义的设计模式。同样,为什么仅仅因为一个成员有一个非平凡的析构函数就将默认析构函数定义为删除?如果仅在联合还具有用户提供的构造函数时适用,我会同意此限制。

问题 1623 的状态为“正在起草”,表明委员会认为该问题可能是一个缺陷 - 为什么要允许工会成员使用大括号或等式初始化程序?- 但尚未花时间确定决议的适当措辞。实际上,该段落在当前的 C++14 草案 N3936 ([class.ctor]/4) 中基本相同,只是“任何直接或虚拟基类或非静态数据成员”的措辞处处被替换为更简单的“任何可能构造的子对象”。

尽管两个编译器的行为并不严格符合,但我认为 Clang 的行为符合标准的精神。看起来 GCC 被删除的默认构造函数和大括号或相等初始化器的组合弄糊涂了:

  • 在没有括号或相等初始化器的情况下,它确实将程序诊断为格式错误,
  • 存在大括号或相等初始化程序和最大警告时,GCC 4.8.2 根本不执行联合初始化,甚至警告成员未初始化

    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 的行为并从大括号或相等初始化器生成适当的构造函数。

于 2014-05-01T02:44:00.790 回答