11.5 Unions
11.5.1 General
【注1:为了简化联合体的使用,提供了一项特殊保证:如果一个标准布局的联合体包含若干个共享相同初始序列([[class.mem]]())的标准布局结构体,并且该标准布局联合体类型对象的某个非静态数据成员处于活动状态,且该活跃成员是这些标准布局结构体之一,那么这些标准布局结构体成员中的任意一个的公共初始序列都可以被检查(`inspected`);参见([[class.mem]]())。——注释结束】
【注2:联合体对象与其非静态数据成员是指针可互换的(`pointer-interconvertible`)([[basic.compound]]()、[[expr.static.cast]]())。因此,联合体对象的所有非静态数据成员具有相同的地址。——注释结束】
联合体可以拥有成员函数(包括构造函数和析构函数),但不能拥有虚函数([class.virtual])。联合体不能有基类,也不能被用作基类。若联合体包含引用类型的非静态数据成员,则程序是非法(ill-formed)的。
【注3:如果联合体包含具有非平凡的拷贝构造函数和(或)移动构造函数([class.copy.ctor])和(或)拷贝赋值运算符和(或)移动赋值运算符([class.copy.assign])的非静态数据成员,则该联合体的对应成员函数必须由用户提供,否则它将会被隐式地删除([dcl.fct.def.delete])。
【示例1:
1 | union U { |
由于
std::string([string.classes])声明了所有特殊成员函数的非平凡版本,因此U的拷贝/移动构造函数以及拷贝/移动赋值运算符将会被隐式删除。尽管std::string具有非平凡的默认构造函数和非平凡的析构函数,但U的默认构造函数和析构函数却都是平凡的。——示例结束】——注释结束】
Paw5zx个人理解:点击查看更多
联合体在默认构造时不会初始化任何成员,因为它没有一个默认活动成员;同样联合体不会析构任何成员,因为它无法知道当前处于活动状态的成员是哪一个。所以编译器在为U生成默认构造函数和默认析构函数时,不会调用s的构造和析构函数,所以U的默认构造函数和析构函数都是平凡的。
但是在gcc中,似乎会直接将默认构造函数和析构函数标记为deleted需要用户手动实现,否则无法通过编译
【注4:这会结束联合体中先前处于活动状态的成员的生命周期(如果有的话)([[basic.life]]())。——注释结束】
【示例2:
1 | union A { int x; int y[4]; }; |
Paw5zx个人理解:点击查看更多
如果赋值表达式的左操作数是对联合体某个成员的访问,那么这个赋值操作可能会隐式启动该成员的生命周期:
1 | union U { |
【示例3:考虑一个联合体类型`U`的对象`u`,联合体`U`具有非静态数据成员`m`(类型为`M`)和`n`(类型为`N`)。 如果`M`具有非平凡的析构函数,而`N`具有非平凡的构造函数(例如,它们声明或继承了虚函数),则可以通过以下方式,利用析构函数和[new表达式](),安全地将`u`的活动成员从`m`切换为`n`:
1 | u.m.~M(); |
11.5.2 Anonymous unions
1 | union { member-specification } ; |
1 | void f() { |
Paw5zx个人理解:点击查看更多
匿名联合体的所有成员会被提升到声明匿名联合体的作用域中,即无需像u.a,u.p这样访问,而是直接a,p访问。
Paw5zx个人理解:点击查看更多
这段话约束了匿名联合体在不同作用域下的存储类规则:
- 如果匿名联合体出现在命名空间作用域(包括全局作用域)中,且具有外部链接(即默认情况下可被其他翻译单元访问),必须加 static:这样做是为了避免匿名联合体的成员名字在全局范围造成链接冲突。
1
static union { int a; float b; }; // OK,a和b具有内部链接
- 如果匿名联合体是局部变量,它的行为就像普通局部变量一样:但是不能使用不允许用于局部变量的存储类(例如
1
2
3
4void f() {
// extern union { int a; float b; }; // 非法:extern在块作用域下的匿名联合体是不允许的
union { int x; float y; }; // OK,不需要任何存储类说明符
}extern) - 如果匿名联合体是类的成员,则不能有任何存储类说明符:
1
2
3struct S {
union { int i; float f; }; // OK
};
1 | void f() { |
Paw5zx个人理解:点击查看更多
根据定义:匿名联合体定义了一个未命名的类型,同时在当前作用域下隐式定义了一个未命名对象,这个对象的所有成员会提升到当前作用域中。
因此对于:
1 | void f() { |
这里虽然写了union { ... }的形式,但后面定义了obj这个对象,因此union { ... }不是匿名联合体而是一个没有名字的类型。所以aa这个成员没有提升到外层作用域,只能通过obj.aa或ptr->aa访问。
1 | union U { |
Paw5zx个人理解:点击查看更多
变体成员指的是联合体内遵循互斥激活规则的成员,这些变体成员共享存储空间,同一时间只能有一个是活动的
- 如果
X是联合体,那么它的所有非匿名联合体的非静态成员都是X的变体成员 - 如果
X是拥有匿名联合体作为直接成员的类,则X的匿名联合体的中的非静态数据成员都是X的变体成员
变体成员共享同一片内存,编译器无法同时在构造函数里初始化两个会互相覆盖的成员,所以最多只能有一个变体成员带默认成员初始化器,否则就会冲突。
在构造联合体对象时,如果没有显式初始化器,则这个带默认初始化器的变体成员会被初始化,并成为活动成员。(待考证)