联合体声明
联合体是特殊的类类型,它在一个时刻只能保有其一个非静态数据成员。
目录 |
[编辑]语法
联合体声明的类说明符与类或结构体的声明相似:
union 属性类头名{ 成员说明} | |||||||||
属性 | - | (C++11 起) 任意数量属性的可选序列 |
类头名 | - | 所定义的联合体的名字。可以前附嵌套名说明符(名字与作用域解析运算符的序列,以作用域解析运算符结尾)。可以忽略名字,此时联合体是无名 的 |
成员说明 | - | 访问说明符、成员对象和成员函数的声明与定义的列表。 |
联合体可以拥有成员函数(包含构造函数和析构函数),但不能有虚函数。
联合体不能有基类且不能用作基类。
(C++11 起) |
联合体不能拥有引用类型的非静态数据成员。
联合体不能含有带非平凡的特殊成员函数的非静态数据成员。 | (C++11 前) | ||||||||
如果联合体含有带非平凡的特殊成员函数的非静态数据成员,那么该联合体的对应特殊成员函数可能会被定义为弃置,详情见对应的特殊成员函数页面。 | (C++11 起) |
如果联合体的成员是拥有用户定义的构造函数和析构函数的类,那么切换其活跃成员通常需要显式析构函数和布置 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;// OK:"c.b.a.y[3]" 指名联合体成员 c.b 与 c.b.a.y;// 这创建对象以保有联合体成员 c.b 和 c.b.a.yreturn c.b.a.y[3];// OK:c.b.a.y 指代新创建的对象} struct X {constint a;int b;};union Y { X x;int k;}; void g(){ Y y ={{1, 2}};// OK,y.x 是联合体的活跃成员int n = y.x.a; y.k=4;// OK:结束 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;constchar* p;}; a =1; p ="Jennifer";}
命名空间作用域的匿名联合体必须声明为 static,除非它们在无名命名空间出现。
[编辑]联合体式的类
联合体式的类 是联合体,或是至少拥有一个匿名联合体成员的(非联合)类。联合体式的类拥有一组变体成员:
- 其成员匿名联合体的非静态数据成员;
- 另外,如果联合体式的类是联合体,那么是其并非匿名联合体的非静态数据成员。
联合体式的类可用于实现带标签联合体。
#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 起) |
[编辑]关键词
[编辑]缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1940 | C++11 | 匿名联合体只允许非静态数据成员 | 也允许 static_assert |
[编辑]引用
- C++23 标准(ISO/IEC 14882:2024):
- 11.5 Unions [class.union]
- C++20 标准(ISO/IEC 14882:2020):
- 11.5 Unions [class.union]
- C++17 标准(ISO/IEC 14882:2017):
- 12.3 Unions [class.union]
- C++14 标准(ISO/IEC 14882:2014):
- 9.5 Unions [class.union]
- C++11 标准(ISO/IEC 14882:2011):
- 9.5 Unions [class.union]
- C++03 标准(ISO/IEC 14882:2003):
- 9.5 Unions [class.union]
- C++98 标准(ISO/IEC 14882:1998):
- 9.5 Unions [class.union]
[编辑]参阅
(C++17) | 类型安全的可辨识联合体 (类模板) |
联合体声明的 C 文档 |