Namespaces
Variants

Union declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

联合体是一种特殊的类类型,它同一时刻只能保有其非静态 数据成员 中的一个。

目录

语法

联合声明的类说明符与 class或struct 声明类似:

union 属性 类头名称 { 成员规范 }
attr - (since C++11) 任意数量的 属性 组成的可选序列
class-head-name - 正在定义的联合体名称。可选择性地前置 nested-name-specifier (由名称和作用域解析运算符组成的序列,以作用域解析运算符结尾)。名称可被省略,此时该联合体为 无名联合体
member-specification - 访问说明符、成员对象及成员函数声明与定义的列表

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

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

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

(since C++11)

联合体不能包含引用类型的非静态数据成员。

联合体不能包含具有非平凡 特殊成员函数 的非静态数据成员。

(C++11 前)

若联合体包含具有非平凡 特殊成员函数 的非静态数据成员,则联合体的对应特殊成员函数可能被定义为弃置,详见相应特殊成员函数页面。

(C++11 起)

正如在 结构体 声明中一样,联合体中的默认成员访问权限是 公开 的。

说明

联合体的大小至少足以容纳其最大的数据成员,但通常不会更大。其他数据成员会被分配在与该最大成员相同的字节区域中。具体分配方式由实现定义,但所有非静态数据成员具有相同的地址。读取最近未写入的联合体成员将导致未定义行为。许多编译器作为非标准语言扩展,实现了读取联合体非活跃成员的能力。

#include <cstdint>
#include <iostream>
union S
{
    std::int32_t n;     // 占用4字节
    std::uint16_t s[2]; // 占用4字节
    std::uint8_t c;     // 占用1字节
};                      // 整个联合体占用4字节
int main()
{
    S s = {0x12345678}; // 初始化第一个成员,s.n现在是活动成员
    // 此时读取s.s或s.c是未定义行为,
    // 但大多数编译器会定义该行为
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s现在是活动成员
    // 此时读取s.n或s.c是未定义行为,
    // 但大多数编译器会定义该行为
    std::cout << "s.c is now " << +s.c << '\n' // 11或00,取决于平台
              << "s.n is now " << s.n << '\n'; // 12340011或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() {} // 需要知道哪个成员是活动的,仅在联合体类中可行
};          // 整个联合体占用 max(sizeof(string), sizeof(vector<int>))
int main()
{
    S s = {"Hello, world"};
    // 此时读取 s.vec 是未定义行为
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // 现在 s.vec 是联合体的活动成员
    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;               // 不启动任何联合体成员的生命周期
    c.b.a.y[3] = 4;    // 正确:"c.b.a.y[3]" 命名了联合体成员 c.b 和 c.b.a.y;
                       // 这会创建对象来持有联合体成员 c.b 和 c.b.a.y
    return c.b.a.y[3]; // 正确:c.b.a.y 引用新创建的对象
}
struct X { const int a; int b; };
union Y { X x; int k; };
void g()
{
    Y y = {{1, 2}}; // 正确,y.x 是活跃联合体成员
    int n = y.x.a;
    y.k = 4;   // 正确:结束 y.x 的生命周期,y.k 成为联合体的活跃成员
    y.x.b = n; // 未定义行为:y.x.b 在其生命周期外被修改,
               // "y.x.b" 命名了 y.x,但 X 的默认构造函数被删除,
               // 因此联合体成员 y.x 的生命周期不会隐式启动
}

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

匿名联合体

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

union { 成员声明序列 } ;

匿名联合体具有更多限制:它们不能拥有成员函数,不能拥有静态数据成员,且所有数据成员必须为公开。唯一允许的声明是非静态数据成员 以及 static_assert 声明 (自 C++11 起)

匿名联合的成员会被注入到外围作用域中(且不得与该作用域中声明的其他名称冲突)。

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

命名空间作用域中的匿名联合必须声明为 static ,除非它们出现在未命名的命名空间中。

联合式类

一个 类联合体 (union-like class)可以是一个联合体(union),或者是一个(非联合体)类且至少拥有一个匿名联合体作为其成员。类联合体具有一组 变体成员 (variant members):

  • 其成员匿名联合体的非静态数据成员;
  • 此外,若该联合式类为联合体,则包含其非匿名联合体的非静态数据成员。

联合式类可用于实现 标签联合

#include <iostream>
// S 拥有一个非静态数据成员(tag),三个枚举成员(CHAR, INT, DOUBLE),
// 以及三个变体成员(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++ 标准。

缺陷报告 应用于 发布时行为 正确行为
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 文档 关于 联合声明