6

正如C++ 标准的(工作草案)所说:

9.5.1 [class.union]

在一个union中,任何时候最多可以有一个非静态数据成员处于活动状态,即任何时候最多可以有一个非静态数据成员的值存储在一个union中。[...]联合的大小足以包含其最大的非静态数据成员。每个非静态数据成员都被分配,就好像它是结构的唯一成员一样。联合对象的所有非静态数据成员具有相同的地址。

但是我不知道如何确定哪个是工会的活跃成员,而且我没有足够的习惯来深入研究标准以找出标准对它的描述,我试图弄清楚活跃成员是如何设置的但我发现它是如何交换的:

9.5.4 [class.union]

[注意:通常,必须使用显式析构函数调用和放置新运算符来更改联合的活动成员。—<em>尾注] [示例:考虑u一个union type U具有非静态数据成员 mMntype的对象N。如果M具有非平凡析构函数和N非平凡构造函数(例如,如果它们声明或继承虚函数),则 u 的活动成员可以安全地从使用析构函数和放置 new 运算符切换mn,如下所示:

u.m.~M();
new (&u.n) N;

—<em>结束示例]

所以我的猜测是,工会的活跃成员是第一个分配、使用、构建或放置新的成员;但这对于统一初始化有点棘手,请考虑以下代码:

union Foo
{
    struct {char a,b,c,d;};
    char array[4];
    int integer;
};

Foo f; // default ctor
std::cout << f.a << f.b << f.c << f.d << '\n';

上面代码中哪个是工会的活跃成员?是std::cout从工会的活跃成员那里阅读吗?下面的代码呢?

Foo f{0,1,2,3}; // uniform initialization
std::cout << f.a << f.b << f.c << f.d << '\n';

使用上面的代码,我们可以初始化嵌套的匿名结构或数组,如果我只提供一个整数,我可以初始化,或者Foo::a......哪个是活动成员?Foo::arrayFoo::integer

Foo f{0}; // uniform initialization
std::cout << f.integer << '\n';

我猜活动成员在上述所有情况下都是匿名结构,但我不确定。

如果我想激活一个或另一个联合成员,我应该提供一个构造函数来激活它吗?

union Bar
{
    // #1 Activate anonymous struct
    Bar(char x, char y, char z, char t) : a(x),b(y),c(z),d(t) {}
    // #2 Activate array
    Bar(char (&a)[4]) { std::copy(std::begin(a), std::end(a), std::begin(array)); }
    // #3 Activate integer
    Bar(int i) : integer(i) {}

    struct {char a,b,c,d;};
    char array[4];
    int integer;
};

我几乎可以肯定 #1 和 #3 会将匿名结构和整数标记为活动联合,但我不知道 #2 因为在我们到达构造函数的主体的那一刻,成员已经被构造了!那么我们是在召唤std::copy一个不活跃的工会成员吗?

问题:

  • Foo如果它是使用以下统一初始化构造的,则 哪些是活动的联合成员:
    • Foo{};
    • Foo{1,2,3,4};
    • Foo{1};
  • 在#2 的构造函数中BarBar::array活跃的联合成员吗?
  • 我可以在标准中的什么地方阅读到哪个是活跃的工会成员,以及如何在没有放置新的情况下设置它?
4

2 回答 2

3

您对工会活跃成员缺乏严格定义的担忧得到了标准化委员会(至少部分)成员的认同 - 请参阅活跃问题 1116描述中的最新说明(日期为 2015 年 5 月) :

我们从不说工会的活跃成员是什么,如何改变它等等。[...]

我认为我们可以期待在未来版本的工作草案中做出某种澄清。该注释还表明,到目前为止,我们最好的是您在问题中引用的段落中的注释,[9.5p4]。

话虽如此,让我们看看你的其他问题。

首先,标准 C++ 中没有匿名结构(只有匿名联合);如果使用相当严格的选项编译(例如,对于 Clang 和 GCC),struct {char a,b,c,d;};将会给你警告。-std=c++1z -Wall -Wextra -pedantic展望未来,我假设我们有一个类似的声明,struct { char a, b, c, d; } s;并且其他所有内容都相应地进行了调整。

第一个示例中的隐式默认默认构造函数不根据 [12.6.2p9.2] 执行任何初始化:

在非委托构造函数中,如果给定的潜在构造子对象不是由mem-initializer-id指定的(包括由于构造函数没有ctor-initializer而没有mem-initializer-list的情况),则

(9.1) - 如果实体是具有大括号或相等初始化器的非静态数据成员,并且

(9.1.1) - 构造函数的类是联合 (9.5),并且该联合的其他变体成员没有由mem-initializer-id指定或
(9.1.2) - 构造函数的类不是联合,并且,如果实体是匿名联合的成员,则该联合的其他成员不会由mem-initializer-id指定,

实体按照 8.5 中的规定进行初始化;

(9.2) - 否则,如果实体是匿名联合或变体成员 (9.5),则不执行初始化;

(9.3) - 否则,实体默认初始化 (8.5)。

我想我们可以说f在其默认构造函数完成执行后没有活动成员,但我不知道有任何标准措辞清楚地表明了这一点。在实践中可以说的是,尝试读取任何f's 成员的值是没有意义的,因为它们是不确定的。

在您的下一个示例中,您正在使用聚合初始化,根据 [8.5.1p16],它为联合定义了合理的定义:

当使用大括号封闭的初始化程序初始化联合时,大括号应仅包含联合的第一个非静态数据成员的初始化子句。[示例:

union u { int a; const char* b; }; 
u a = { 1 }; 
u b = a; 
u c = 1;               // error 
u d = { 0, "asdf" };   // error 
u e = { "asdf" };      // error 

结束示例]

这与[ 8.5.1p12 ] 中指定的用于初始化嵌套结构的大括号省略一起使结构成为活动成员。它也回答了您的下一个问题:您只能使用该语法初始化第一个联合成员。

你的下一个问题:

如果我想激活一个或另一个联合成员,我应该提供一个构造函数来激活它吗?

是的,或者根据上面引用的 [12.6.2p9.1.1] 只为一个成员使用大括号或相等初始化器;像这样的东西:

union Foo
{
    struct { char a, b, c, d; } s;
    char array[4];
    int integer = 7;
};

Foo f;

在上述之后,活跃成员将是integer。以上所有内容也应该回答您的问题#2(当我们到达构造函数的主体时,成员尚未构造 -#2也很好)。

结束,Foo{}Foo{1}执行聚合初始化;它们分别被解释为Foo{{}}Foo{{1}}(因为大括号省略),并初始化结构;根据[8.5.1p7],第一个将所有结构成员设置为0,第二个将第一个成员设置为1,其余设置为。0


所有标准报价均来自当前工作草案N4527


论文N4430处理了一些相关的问题,但尚未整合到工作草案中,它为活跃成员提供了一个定义:

在联合中,如果非静态数据成员的名称引用其生命周期已经开始但尚未结束的对象 ([basic.life]),则它是活动的。

这有效地将责任推给了 [3.8] 中的生命周期定义,该定义也有一些针对它的问题,包括前面提到的问题 1116,所以我认为我们必须等待几个这样的问题得到解决才能解决有一个完整和一致的定义。目前对生命周期的定义似乎还没有完全准备好。

于 2015-07-13T18:06:03.260 回答
0

活跃成员是您写信给的最后一个成员。就那么简单。

该术语不是由 C++ 定义的,因为它是由英语定义的。

于 2015-07-13T16:07:41.470 回答