Union其实是不想介绍的一个关键字,因为union的功能即将被C++17标准中的variant所代替,而且variant更加安全。但是还是要在这里介绍一下。
union(联合体)和struct相似,也可以包含多个数据成员,但是不同的是同时只允许一个成员有效,因此经常作为作为节约空间的类使用。
C++11中union除了继承c语言的数据共享内存之外,行为上越来越像一个类,比如成员默认是public类型。
在C++11以后,很多基础语法都进行了修正。其中 union 的行为向类对象进行了发展,在兼容原有语法定义的基础上进行了扩充:
-
联合体可拥有成员函数(包含构造函数和析构函数),但不能有虚函数。
-
联合体不能有基类且不能用作基类。
-
联合体不能拥有引用类型的非静态数据成员。
联合体不能含有带非平凡特殊成员函数(复制构造函数、复制赋值运算符或析构函数)的非静态数据成员。(C++11 前) |
若联合体含有带非平凡特殊成员函数(复制/移动构造函数,复制/移动赋值,或析构函数)的非静态数据成员,则联合体中的该函数默认被弃置,且需要程序员显式定义它。若联合体含有带非平凡默认构造函数的非静态数据成员,则该联合体的默认构造函数默认被弃置,除非联合体的变体成员拥有一个默认成员初始化器。至多一个变体成员可以拥有默认成员初始化器。(C++11 起) |
这些说明都很难懂,举两个小例子:
struct Point{
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U{
int z;
double w;
Point p; // 在C++03中是不合法(point有一non-trivial建構式),但是在C++11是合法的
U() {} // 由于 Point 成员的存在,必须要定义一个构造函数
U(const Point& pt) : p(pt) {} // 通过初始化列表构造 Point 对象
U& operator=(const Point& pt) { new (&p) Point(pt); return *this; } // 通过原地new方式赋值构造Point对象
};
#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
需要注意的是
联合体的默认成员访问是 public。
解释
联合体的大小仅足以保有其最大的数据成员。其他数据成员分配于该最大成员的一部分相同的字节。分配的细节是实现定义的,且从并非最近写入的联合体成员进行读取是未定义行为。许多编译器作为非标准语言扩展,实现读取联合体的不活跃成员的能力。
#include <iostream>
#include <cstdint>
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 现在是活跃成员
// 在此点,从 n 或 c 读取是 UB 但大多数编译器都对其有定义
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
各个成员都如同它是类的仅有成员一样进行分配。
可以看出,使用union存在着一些隐患,这是由于union虽然能节约空间。但是它没有类型信息。
关注公众号获取更多信息: