命名空间
变体
操作

联合声明

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 

联合是一种特殊的类类型,它一次只能容纳其非静态数据成员中的一个。

内容

[编辑] 语法

联合声明的类说明符类似于类或结构体声明

union attr class-head-name { member-specification }
attr - (自 C++11 起) 可选的任意数量的属性序列
class-head-name - 正在定义的联合的名称。可选地用nested-name-specifier(名称和作用域解析运算符的序列,以作用域解析运算符结束)作为前缀。名称可以省略,在这种情况下,联合是未命名的
member-specification - 访问说明符、成员对象和成员函数声明以及定义的列表。

联合可以具有成员函数(包括构造函数和析构函数),但不能具有虚函数。

联合不能具有基类,也不能用作基类。

联合不能具有引用类型的非静态数据成员。

联合不能包含具有非平凡特殊成员函数的非静态数据成员(复制构造函数复制赋值运算符或析构函数)。

(直到 C++11)

如果联合包含具有非平凡特殊成员函数(复制/移动构造函数、复制/移动赋值或析构函数)的非静态数据成员,则该函数默认情况下在联合中被删除,并且需要由程序员显式定义。

如果联合包含具有非平凡默认构造函数的非静态数据成员,则联合的默认构造函数默认情况下被删除,除非联合的变体成员具有默认成员初始化器。

最多一个变体成员可以具有默认成员初始化器

(自 C++11 起)

就像在struct声明中一样,联合中的默认成员访问是公有的

[编辑] 说明

联合的大小至少与它最大的数据成员一样大,但通常不会更大。其他数据成员旨在分配在与该最大成员相同的字节中。这种分配的细节是实现定义的,但所有非静态数据成员具有相同的地址。从最近一次写入的联合成员中读取是未定义的行为。许多编译器实现读取联合的非活动成员的能力,作为一种非标准的语言扩展。

#include <cstdint>
#include <iostream>
 
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};                      // the whole union occupies 4 bytes
 
int main()
{
    S s = {0x12345678}; // initializes the first member, s.n is now the active member
    // at this point, reading from s.s or s.c is undefined behavior
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s is now the active member
    // at this point, reading from n or c is UB but most compilers define it
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}

可能的输出

s.n = 12345678
s.c is now 0
s.n is now 115678

每个成员都被分配,就好像它是类中唯一的成员一样。

如果联合的成员是具有用户定义的构造函数和析构函数的类,则要切换活动成员,通常需要显式析构函数和放置 new。

#include <iostream>
#include <string>
#include <vector>
 
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))
 
int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

输出

s.str = Hello, world
1
(自 C++11 起)

如果两个联合成员是标准布局类型,则在任何编译器上检查它们的公共子序列都是定义良好的。

[编辑] 成员生存期

联合成员的生存期从成员变为活动时开始。如果之前有其他成员处于活动状态,则其生存期结束。

当联合的活动成员通过以下形式的赋值表达式切换时,E1 = E2 使用内置赋值运算符或平凡赋值运算符,对于E1 的成员访问和数组下标子表达式中出现的每个联合成员 X,该成员不是具有非平凡或已删除的默认构造函数的类,如果对 X 的修改在类型别名规则下将具有未定义的行为,则会在指定存储中隐式创建类型为 X 的对象;不执行初始化,其生存期开始时间在左操作数和右操作数的值计算之后和赋值之前。

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
 
int f()
{
    C c;               // does not start lifetime of any union member
    c.b.a.y[3] = 4;    // OK: "c.b.a.y[3]", names union members c.b and c.b.a.y;
                       // This creates objects to hold union members c.b and c.b.a.y
    return c.b.a.y[3]; // OK: c.b.a.y refers to newly created object
}
 
struct X { const int a; int b; };
union Y { X x; int k; };
 
void g()
{
    Y y = { { 1, 2 } }; // OK, y.x is active union member
    int n = y.x.a;
    y.k = 4;   // OK: ends lifetime of y.x, y.k is active member of union
    y.x.b = n; // undefined behavior: y.x.b modified outside its lifetime,
               // "y.x.b" names y.x, but X's default constructor is deleted,
               // so union member y.x's lifetime does not implicitly start
}

联合类型的平凡移动构造函数、移动赋值运算符、(自 C++11 起)复制构造函数和复制赋值运算符复制对象表示。如果源和目标不是同一个对象,这些特殊成员函数将启动目标中每个对象的生存期(除了既不是目标子对象也不是隐式生存期类型的对象),这些对象对应于源中嵌套的对象,在复制执行之前。否则,它们不执行任何操作。两个联合对象在通过平凡特殊函数进行构造或赋值后具有相同的相应活动成员(如果有)。

[编辑] 匿名联合

匿名联合是未命名的联合定义,它不会同时定义任何变量(包括联合类型对象、引用或指向联合的指针)。

union { member-specification } ;

匿名联合有进一步的限制:它们不能具有成员函数,不能具有静态数据成员,并且所有数据成员必须是公共的。允许的唯一声明是非静态数据成员 static_assert 声明(自 C++11 起)

匿名联合的成员被注入到封闭的范围中(并且不能与在那里声明的其他名称冲突)。

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Jennifer";
}

命名空间范围的匿名联合必须声明为静态的,除非它们出现在未命名的命名空间中。

[编辑] 类似联合的类

类似联合的类是联合,或至少有一个匿名联合作为成员的(非联合)类。类似联合的类具有一组变体成员

  • 其成员匿名联合的非静态数据成员;
  • 此外,如果类似联合的类是联合,则其不是匿名联合的非静态数据成员。

类似联合的类可用于实现带标签的联合

#include <iostream>
 
// S has one non-static data member (tag), three enumerator members (CHAR, INT, DOUBLE), 
// and three variant members (c, i, d)
struct S
{
    enum{CHAR, INT, DOUBLE} tag;
    union
    {
        char c;
        int i;
        double d;
    };
};
 
void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.i << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}
 
int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT;
    s.i = 123;
    print_s(s);
}

输出

a
123

C++ 标准库包括 std::variant,它可以替代许多联合和类似联合类的用途。上面的示例可以改写为

#include <iostream>
#include <variant>
 
int main()
{
    std::variant<char, int, double> s = 'a';
    std::visit([](auto x){ std::cout << x << '\n';}, s);
    s = 123;
    std::visit([](auto x){ std::cout << x << '\n';}, s);
}

输出

a
123
(自 C++17 起)

[编辑] 关键字

union

[编辑] 缺陷报告

以下行为更改缺陷报告已追溯应用于之前发布的 C++ 标准。

DR 应用于 发布的行为 正确行为
CWG 1940 C++11 匿名联合只允许非静态数据成员 static_assert 也允许

[编辑] 参考文献

  • C++23 标准 (ISO/IEC 14882:2024)
  • 11.5 联合 [class.union]
  • C++20 标准 (ISO/IEC 14882:2020)
  • 11.5 联合 [class.union]
  • C++17 标准 (ISO/IEC 14882:2017)
  • 12.3 联合 [class.union]
  • C++14 标准 (ISO/IEC 14882:2014)
  • 9.5 联合 [class.union]
  • C++11 标准 (ISO/IEC 14882:2011)
  • 9.5 联合 [class.union]
  • C++03 标准 (ISO/IEC 14882:2003)
  • 9.5 联合 [class.union]
  • C++98 标准 (ISO/IEC 14882:1998)
  • 9.5 联合 [class.union]

[编辑] 另请参阅

(C++17)
类型安全的区分联合
(类模板) [编辑]
C 文档 用于 联合声明