联合体声明
联合体是一种特殊的类类型,它一次只能保存其非静态数据成员中的一个。
目录 |
[edit] 语法
联合体声明的类说明符类似于类或结构体声明
union attr class-head-name { member-specification } |
|||||||||
attr | - | (since C++11) 可选的任意数量的属性 |
class-head-name | - | 被定义的联合体的名称。 可选地以 nested-name-specifier(名称和作用域解析运算符的序列,以作用域解析运算符结尾)为前缀。 名称可以省略,在这种情况下,联合体是匿名的 |
member-specification | - | 访问说明符、成员对象和成员函数声明和定义的列表。 |
联合体可以有成员函数(包括构造函数和析构函数),但不能有虚函数。
联合体不能有基类,也不能用作基类。
(since C++11) |
联合体不能有引用类型的非静态数据成员。
联合体不能包含具有非平凡特殊成员函数的非静态数据成员。 |
(until C++11) |
如果联合体包含具有非平凡特殊成员函数的非静态数据成员,则联合体的相应特殊成员函数可以定义为已删除,有关详细信息,请参阅相应的特殊成员函数页面。 |
(since C++11) |
就像在struct声明中一样,联合体中的默认成员访问是public。
[edit] 解释
联合体的大小至少与容纳其最大的数据成员所需的大小相同,但通常不会更大。 其他数据成员旨在与该最大成员的一部分一起分配在相同的字节中。 该分配的详细信息是实现定义的,但所有非静态数据成员都具有相同的地址。 从最近未写入的联合体成员读取是未定义行为。 许多编译器实现了一种非标准语言扩展,即读取联合体的非活动成员的能力。
#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, // but most compilers define it. 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 s.n or s.c is undefined behavior, // 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
每个成员的分配都如同它是该类的唯一成员。
如果联合体的成员是具有用户定义构造函数和析构函数的类,则要切换活动成员,通常需要显式析构函数和 placement 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 |
(since C++11) |
如果两个联合体成员是标准布局类型,则在任何编译器上检查它们的公共子序列都是良好定义的。
[edit] 成员生存期
联合体成员的生存期在成员变为活动状态时开始。 如果先前有另一个成员处于活动状态,则其生存期结束。
当联合体的活动成员通过形式为 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 }
联合体类型的平凡移动构造函数、移动赋值运算符、(since C++11)复制构造函数和复制赋值运算符复制对象表示。 如果源和目标不是同一对象,则这些特殊成员函数在执行复制之前启动嵌套在目标中的每个对象(除了既不是目标的子对象也不是隐式生存期类型的对象)的生存期,这些对象对应于嵌套在源中的对象。 否则,它们不执行任何操作。 通过平凡特殊函数构造或赋值后,两个联合体对象具有相同的相应活动成员(如果有)。
[edit] 匿名联合体
匿名联合体是未命名的联合体定义,它不同时定义任何变量(包括联合体类型的对象、引用或指向联合体的指针)。
union { member-specification } ; |
|||||||||
匿名联合体有更多限制:它们不能有成员函数,不能有静态数据成员,并且它们的所有数据成员都必须是公共的。 唯一允许的声明是非静态数据成员 和 static_assert
声明(since C++11)。
匿名联合体的成员被注入到封闭作用域中(并且不得与那里声明的其他名称冲突)。
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
命名空间作用域的匿名联合体必须声明为 static,除非它们出现在未命名的命名空间中。
[edit] 类联合体类
类联合体类是联合体,或至少有一个匿名联合体作为成员的(非联合体)类。 类联合体类有一组变体成员
- 其成员匿名联合体的非静态数据成员;
- 此外,如果类联合体类是联合体,则其不是匿名联合体的非静态数据成员。
类联合体类可用于实现标签联合。
#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 |
(since C++17) |
[edit] 关键字
[edit] 缺陷报告
以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 1940 | C++11 | 匿名联合体仅允许非静态数据成员 | 也允许 static_assert |
[edit] 参考
- 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]
[edit] 参见
(C++17) |
类型安全的区分联合 (class template) |