联合体的活跃成员,统一的初始化和构造函数

Active member of an union, uniform initialization and constructors

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

9.5.1 [class.union]

In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [...] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. All non-static data members of a union object have the same address.

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

9.5.4 [class.union]

[ Note: In general, one must use explicit destructor calls and placement new operators to change the active member of a union. —end note ] [Example: Consider an object u of a union type U having non-static data members m of type M and n of type N. If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement new operator as follows:

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

end example ]

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

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

问题:

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

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

标准化委员会的(至少部分)成员也对工会的活跃成员缺乏严格定义感到担忧 - 查看最新消息active issue 1116:

描述中的注释(日期为 2015 年 5 月)

We never say what the active member of a union is, how it can be changed, and so on. [...]

我认为我们可以期待在工作草案的未来版本中进行某种澄清。该注释还表明我们迄今为止最好的是您在问题中引用的段落中的注释,[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] 执行任何初始化:

In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

(9.1) - if the entity is a non-static data member that has a brace-or-equal-initializer and either

(9.1.1) - the constructor’s class is a union (9.5), and no other variant member of that union is designated by a mem-initializer-id or
(9.1.2) - the constructor’s class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id,

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

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

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

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

在您的下一个示例中,您将使用 聚合初始化,根据 [8.5.1p16]:

,这对于联合来说是合理定义的

When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union. [ Example:

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 

end example ]

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

你的下一个问题:

If I want to activate one or the other union member, should I provide a constructor activating it?

是,或者 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].[=36=,第一个将所有结构成员设置为 0,第二个将第一个成员设置为 1,其余设置为 0 ]


所有标准引用均来自当前工作草案,N4527


论文 N4430,它处理了一些相关的问题,但还没有被整合到工作草案中,为 active member 提供了定义:

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]).

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