非受限联合体
文章参考:
1. 联合体
联合体又名共用体,使用方式和struct
相似,其特点在于:
- 联合体中所有的成员变量,引用的都是内存中的相同位置。
- 如果联合的不同成员有不同的长度,取最长的那个变量作为联合的长度。
- 如果将结构体作为联合的成员变量,那么联合的大小取决于变量中最大的结构体的大小。
EG:
struct VARIABLE_UNION{
enum { INT, FLOAT, STRING } type;
union {
int int_value;
float float_value;
char *string_value;
} value;
};
局限性:
- 不允许联合体拥有
非POD
类型的成员。 - 不允许联合体拥有静态函数。
- 不允许联合体拥有引用类型的成员。
非受限联合体:
在C++11中,取消了上述关于联合体的局限性,重新规定任何非引用类型都可以成为联合体的数据成员
,这就是非受限联合体。
2. 非受限联合体
2.1 静态类型的成员
非受限联合体中,静态成员有两种:
- 静态成员变量。
- 静态成员函数。
EG:
-
代码:
#include <iostream> using namespace std; union Test { int age; long id; // int& temp = age; // error // static char c = 'a'; // error static char c; static int print() { // cout << "age = " << age << endl; // error cout << "c=" << c << endl; return 1; }; }; char Test::c = 'a'; int main(void){ Test t; t.age = 1; t.id = 2l; t.c = 'x'; cout << "age=" << t.age << endl; cout << "id=" << t.id << endl; cout << "c=" << t.c << endl; cout << "sizeof(union)=" << sizeof(Test) << endl; return 0; }
-
输出:
age=2 id=2 c=x sizeof(union)=8
-
分析:
- 第7行:错误。即使是非受限联合体,依旧禁止使用用引用作为成员。
- 第8行:错误。
non-const static data numbet must be initilized out of line
(非常量静态数据成员的初始化必须在行外),因此在16行进行了初始化。 - 第11行:错误。静态成员函数只能访问静态成员。
- 第23行:因为
age
和id
共用一块内存,而id的赋值在age之后,因此age原本的值被id的值覆盖了。 - 第24行:id的值覆盖了age的值。但id的值并没有被c覆盖,这说明静态成员变量和非静态成员变量使用的不是一块内存。
- 第28行:说明联合体的大小依旧由非静态成员决定。
2.2 非POD类型成员
在C++11中,如果某受限联合体有一个非POD的成员
,而且该成员拥有非平凡的构造函数/拷贝构造哈桑农户/拷贝复制操作符/移动构造函数/移动赋值操作符/析构函数
,那么该受限联合体的默认析构函数将会被删除
。
#include <iostream>
using namespace std;
class Base{
public:
Base(); // 非平凡的构造函数,因此Base是非POD类型
};
union Test {
int a;
Base b;
}
int main(void){
// Test t; // error
return 0;
}
如上所示,由于联合体Test
的默认构造函数已经被删除,因此第15行报错。在这种情况下,我们要为非受限联合体定义构造函数
,这时我们需要用到定位放置new
操作。
2.2.1 placement new
一般情况下,我们使用new申请空间,这时会从系统的堆(heap)
中分配空间,申请所得的空间的位置是根据当时的内存实际使用决定的。但在有些时候,我们要在已经分配的特定内存创建对象
,这种操作就是placement new
,也就是定位放置 new
。
语法:
ClassName* ptr = new (address of memory)ClassName(parameter list);
Eg:
-
代码:
#include <iostream> using namespace std; class Base{ public: int num; Base(); // 非平凡的构造函数,因此Base是非POD类型 }; int main(void){ int n = 100; Base* b = new (&n)Base(); cout << b->num << endl; char c = 'a'; // Base* b1 = new (&c)Base; // error return 0; }
-
输出:
100
-
分析:
- 第12行:将变量
n
的地址分配给指针b
,此时指针b指向的内存和变量n对应的内存是同一块栈内存。 - 第13行:输出100是因为b指向的地址就是n的地址,而
Base类
的成员变量number
的歧视地址和Base对象的起始地址是相同的,所以打印出的number
的值和n
的值相同。 - 第15行:错误。这是因为
Base
需要的内存为4个字节,而char
的内存只有1个字节,内存大小不够,无法初始化。
- 第12行:将变量
特点:
放置new
操作,可以在栈/堆上生成对象。放置new
操作并不是新申请了一份空间,而是利用已经申请好的空间,因此该空间大小必须>=类所需空间大小
。- 使用
放置new
操作创建对象会自动调用对应的构造函数,但是由于对象的空间不会自动释放,因此如果需要释放堆内存则必须显式调用类的析构函数
。 - 使用
放置new
操作,我们可以反复利用同一块堆内存,而不用多次创建销毁堆内存,这样可以提高程序的执行效率。(例如网络通信中数据的接收和发送)。
2.2.2 自定义非受限联合体构造函数
通过使用placement new
操作,我们可以自定义非受限联合体构造函数。
EG:
-
代码:
#include <iostream> #include <string> using namespace std; class Base { public: string msg; Base(){ cout << "non-parameter constructor" << endl; } Base(string a): msg(a){ cout << "parameterized constructor " << endl; } void print(){ cout << msg << endl; } }; union Test { string msg; int a; Base b; Test(){ new (&msg)string; } ~Test(){} }; int main(void){ Test t; t.msg = "aaa"; t.b.msg = "bbb"; cout << t.msg << endl; cout << t.b.msg << endl; t.b.print(); return 0; }
-
输出:
bbb bbb bbb
-
分析:
- 第23行:为非受限联合体指定构造函数,通过
placement new
的方式将构造出的对象地址指定到了联合体成员msg
上,这样联合体内内部其余非静态成员也可以访问这一块内存了。 - 第30行:此时联合体成员变量
b
的位置和msg
的位置一致,而变量b是Base
类型,Base的成员变量msg
的首地址和Base
的首地址一致,总结下来就就是&t.b.msg==&t.msg
,因此对t.b.msg
的修改会覆盖第29行的操作。
- 第23行:为非受限联合体指定构造函数,通过
2.2.3 匿名的非受限联合体
一般情况下我们使用的联合体都是有名字的,有时我们也可以使用匿名的非受限联合体,一个比较实用的场景就是配合类的定义使用。
EG:
-
场景:现在对某个村子进行人口普查,人口登记方式如下:
- 学生只登记所在学校的编号。
- 本村除了学生以外的人,登记身份证号。
- 外来人员,登记:户口所在地、联系方式。
-
代码:
#include <iostream> #include <string> using namespace std; // 外来人口信息 struct Foreigner { Foreigner(string s, string ph) : addr(s), phone(ph) {} string addr; string phone; }; // 人口类型 enum class Category : char { Student, Local, Foreign }; // 登记人口信息 class Person { private: Category type; union { int number; string idNum; Foreigner foreign; }; public: Person(int num) : number(num), type(Category::Student) {} Person(string id) : idNum(id), type(Category::Local) {} Person(string addr, string phone) : foreign(addr, phone), type(Category::Foreign) {} ~Person() {} void print() { cout << "Person category: " << (int)type << endl; switch (type) { case Category::Student: cout << "Student school number: " << number << endl; break; case Category::Local: cout << "Local people ID number: " << idNum << endl; break; case Category::Foreign: cout << "Foreigner address: " << foreign.addr << ", phone: " << foreign.phone << endl; break; default: break; } } }; int main() { cout << sizeof(string) << endl; cout << sizeof(Category) << endl; cout << sizeof(Foreigner) << endl; cout << sizeof(Person) << endl; return 0; }
-
输出:
32 1 64 72
-
分析:
- 第25行:定义匿名非受限联合体,
Person
类可以直接访问非受限联合体内部的数据成员
。 - 非受限联合体中,
number
、idNum
、foreign
三者共用一块内存。从而节省了空间。 - 为什么
Person
的大小为72,而不是64+1=65
?这涉及到C/C++中的字节对齐
问题,此处不展开讲述。
- 第25行:定义匿名非受限联合体,