c++ union
c++的union,单纯考虑c++11之后的union。c++11之前的union,只能是pod类型。
c++11之后允许有非pod类型的union,但是有一些限制。
联合体可以拥有成员函数(包含构造函数和析构函数),但不能有虚函数。
联合体不能有基类且不能用作基类。
联合体不能拥有引用类型的非静态数据成员。
C++11 规定,如果非受限联合体内有一个非 POD 的成员,而该成员拥有自定义的构造函数,那么这个非受限联合体的默认构造函数将被编译器删除;其他的特殊成员函数,例如默认拷贝构造函数、拷贝赋值操作符以及析构函数等,也将被删除
另外union的访问属性和struct的是一致的,实测发现多非POD类型union的构造用initialize list在有些情况下可行,不需要自定义构造函数,但是析构函数还是要写
另外如果没有显示的释放调用非POD类型的析构函数,这个类的内存其实是没有释放的。
如果要激活union中的多个非POD类型,可能还需要配合placement new
比如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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();
}
自定义构造析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <string>
using namespace std;
union U {
string s;
int n;
public:
U() { new(&s) string; }
~U() { s.~string(); }
};
int main() {
U u;
return 0;
}
构造时,采用 placement new 将 s 构造在其地址 &s 上,这里 placement new 的唯一作用只是调用了一下 string 类的构造函数。注意,在析构时还需要调用 string 类的析构函数。
placement new 是 new 关键字的一种进阶用法,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。相对应地,我们把常见的 new 的用法称为 operator new,它只能在 heap 上生成对象。
1
new(address) ClassConstruct(...)
address 表示已有内存的地址,该内存可以在栈上,也可以在堆上;ClassConstruct(…) 表示调用类的构造函数,如果构造函数没有参数,也可以省略括号。
placement new 利用已经申请好的内存来生成对象,它不再为对象分配新的内存,而是将对象数据放在 address 指定的内存中。在本例中,placement new 使用的是 s 的内存空间。
生存周期
联合体成员的生存期从该成员被设为活跃(active)时开始。如果之前已经有另一成员活跃,那么它的生存期终止。
当联合体的活跃成员通过形式为 E1 = E2 的复制表达式(使用内建赋值运算符或平凡的赋值运算符)切换时,对于 E1 中的各个成员访问和数组下标子表达式中出现的,其类型并非拥有非平凡或弃置的默认构造函数的类的每个联合体成员 X,如果 X 的修改在类型别名使用规则下会具有未定义行为,那么在所指名的存储中隐式创建一个 X 类型的对象;不进行初始化,且其生存期的开始按顺序晚于其左右的操作数的值计算,而早于赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.y
return c.b.a.y[3]; // OK:c.b.a.y 指代新创建的对象
}
struct X { const int 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 的生存期不会隐式开始
}