【问题标题】:Active member of an union, uniform initialization and constructors联合的活跃成员,统一初始化和构造函数
【发布时间】:2015-10-02 00:04:29
【问题描述】:

正如(Working Draft of) C++ Standard 所说:

9.5.1 [class.union]

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

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

9.5.4 [class.union]

[ 注意:通常,必须使用显式析构函数调用和放置新运算符来更改联合的活动成员。 —结束说明 ] [示例:考虑一个对象uunion type U 具有类型为Mn 的非静态数据成员m N。如果M 有一个非平凡的析构函数并且N 有一个非平凡的构造函数(例如,如果它们声明或继承虚函数),则 u 的活动成员可以安全地从m 切换到n使用析构函数和放置 new 操作符如下:

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

结束示例 ]

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

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::aFoo::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};
  • Bar 的#2 构造函数中,Bar::array 是活动的联合成员吗?
  • 我可以在标准中的哪个位置了解哪个是活动工会成员以及如何在不放置新成员的情况下设置它?

【问题讨论】:

    标签: c++ constructor unions uniform-initialization


    【解决方案1】:

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

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

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

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

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

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

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

    (9.1) - 如果实体是具有 brace-or-equal-initializer

    的非静态数据成员

    (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 的任何成员的值是没有意义的,因为它们是不确定的。

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

    当使用大括号括起来的初始化程序初始化联合时, 大括号应仅包含第一个 initializer-clause 联合体的非静态数据成员。 [ 例子:

    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] 中指定的用于初始化嵌套结构的 brace elision 一起使该结构成为活动成员。它也回答了您的下一个问题:您只能使用该语法初始化第一个联合成员。

    你的下一个问题:

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

    是的,或者一个 brace-or-equal-initializer 根据上面引用的 [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] 中的生命周期定义,该定义也存在一些针对它的问题,包括前面提到的issue 1116,所以我认为我们必须等待几个这样的问题出现解决,以便有一个完整和一致的定义。就目前而言,生命周期的定义似乎还没有完全成熟。

    【讨论】:

    • 带有明确示例的非常扩展的答案 :) 作为引用来源提到的文档在这里:N4527
    • @PaperBirdMaster 我刚刚偶然发现一篇试图提供定义的论文,我在答案中添加了对它的引用。我还添加了工作草案的链接——把它放在那里是个好主意。
    【解决方案2】:

    活动成员是您写信的最后一个成员。就这么简单。

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

    【讨论】:

    • 不应该回答原帖末尾的问题吗?
    • @Jashaszun:这些信息涵盖了所有内容。如果 OP 希望将它们视为不同的问题,那么他应该这样发布它们。
    • 很公平。毕竟,我想这真的很简单。
    • 对不起,我缺乏搜索技能... :( 我无法找到声明活动成员是最后一个写的成员的位置。
    • @PaperBirdMaster:“这个术语不是由 C++ 定义的,因为它是由英文定义的。”
    猜你喜欢
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-23
    • 2011-05-02
    • 1970-01-01
    • 2017-04-17
    相关资源
    最近更新 更多