联合体是一种在C/C++中的构造类型的数据结构,联合体内可定义多种不同数据类型,它们会共享内存空间。
在C++98中,非POD类型成员的联合体无法通过编译,例如:
struct Student{
//定义了构造函数,student成为非pod类型
Student(bool g, int a): gender(g), age(a){}
bool gender;
int age;
};
union T
{
Student s; // 编译失败,不是一个POD类型
int id;
char name[10];
};
// 编译选项:g++ -std=c++98 3-7-1.cpp
不仅包含非pod类型的联合体无法通过编译,C++98标准也不允许联合体拥有静态或引用类型的成员。本意是为了与C尽可能兼容,但使联合体的使用受限。
非受限联合体:
实践证明C++98对联合体的限制没有什么意义,而且随C++发展,有些东西已经和C发生了本质上的区别,没必要再为C兼容联合体,因此新标准(C++11)规定,任何非引用类型(也就是不限制非POD和静态)都可成为联合体成员,这样的联合体就叫非受限联合体
C++98规定联合体自动对未在初始化成员列表中出现的成员赋默认初值,但由于联合体同一时间只有一个成员生效,那么初始化联合体时到底是初始化了第一个成员还是所有成员?
例子:
union T
{
int x;
double d;
char b[sizeof(double)];
};
T t = {0}; // 到底是初始化第一个成员还是所有成员呢?
// 编译选项:g++ -std=c++98-c 3-7-3.cpp
int是第一个成员,实际上C++98在对t赋值时,是将联合体大小的所有空间(即并非只对Int的4个字节赋值为0,而是整个结构体的8字节)都设置成了0,实际上我们并不都希望它这么干,也不理解它为什么要这么干,因此C++新标准中会删除一些非受限联合体的默认构造函数,减少这种情况的发生。比如当一个非受限联合体存在一个非Pod类型成员,该成员拥有一个非平凡的构造函数,那么这个非受限联合体的默认构造函数将被删除。类似地,默认拷贝构造函数、拷贝赋值操作符、以及析构函数等,也都遵循此规则
举例 :
#include <string>
using namespace std;
union T
{
string s; // 满足string是非POD类型并且string有非平凡的构造函数 ,T的默认构造函数被删除
int n;
};
int main()
{
T t; // 构造失败,因为T的构造函数被删除了
}
// 编译选项:g++ -std=c++11 3-7-4.cpp
出现上面这种情况时,需要手动给非受限联合体定义默认构造函数(其它特殊函数规则类似)
像这样:
#include <string>
using namespace std;
union T
{
string s;
int n;
public:
// 自定义构造函数和析构函数
T()
{
new (&s) string;
}
~T()
{
s.~string();
}
};
int main()
{
T t; // 构造析构成功
}
// 编译选项:g++ -std=c++11 3-7-5.cpp
上面代码构造T时,采用placement new将s构造在其地址&s上
[关于placement new 可参阅这篇文献:https://www.cnblogs.com/xzlq/p/9504851.html]
,这里placement new的唯一作用只是调用了一下string的构造函数;在析构时,Union T也必须是一个string对象,否则可能导致析构错误
匿名非受限联合体可以运用于类的声明中,这样的类也被称为**“枚举式的类”**(union-like class)
举例:
#include <cstring>
using namespace std;
//有自己的构造,非POD
struct Student
{
Student(bool g, int a): gender(g), age(a){}
bool gender;
int age;
};
class Singer
{
public:
enum Type
{
STUDENT,
NATIVE,
FOREIGNER
};
Singer(bool g, int a): s(g, a)
{
t = STUDENT;
}
Singer(int i): id(i)
{
t = NATIVE;
}
Singer(const char* n, int s)
{
int size = (s > 9) ? 9 : s;
memcpy(name, n, size);
name[s] = '\0';
t = FOREIGNER;
}
~Singer() {}
private:
Type t;
union
{
// 匿名的非受限联合体
Student s;
int id;
char name[10];
};
};
int main()
{
Singer(true, 13);
Singer(310217);
Singer("J Michael", 9);
}
// 编译选项:g++ -std=c++11 3-7-6.cpp
上面的匿名非受限联合体称为类Singer的“变长成员”(variant member)。变长成员给类的编写带来了更大的灵活性,这是C++98标准中无法达到的