非限定性资产怎么计算(C++11:非受限联合体(union))
在 C/C++ 中 ,联合体(Union)是一种构造数据类型 。在一个联合体内 ,我们可以定义多个不同类型的成员 ,这些成员将会共享同一块内存空间 。老版本的 C++ 为了和C语言保持兼容 ,对联合体的数据成员的类型进行了很大程度的限制 ,这些限制在今天看来并没有必要 ,因此 C++11 取消了这些限制 。
C++11 标准规定 ,任何非引用类型都可以成为联合体的数据成员 ,这种联合体也被称为非受限联合体 。例如:
class Student{ public: Student(bool g, int a): gender(g), age(a) {} private: bool gender; int age; }; union T{ Student s; // 含有非POD类型的成员 ,gcc-5.1.0 版本报错 char name[10]; }; int main(){ return 0; }上面的代码中 ,因为 Student 类带有自定义的构造函数 ,所以是一个非 POD 类型的 ,这导致编译器报错 。这种规定只是 C++ 为了兼容C语言而制定,然而在长期的编程实践中发现 ,这种规定是没有必要的 。
1. C++11 允许非 POD 类型
C++98 不允许联合体的成员是非 POD 类型 ,但是 C++11 取消了这种限制 。
POD 是 C++ 中一个比较重要的概念,POD 是英文 Plain Old Data 的缩写 ,用来描述一个类型的属性 。POD 类型一般具有以下几种特征(包括 class 、union 和 struct等):
没有用户自定义的构造函数 、析构函数 、拷贝构造函数和移动构造函数 。
不能包含虚函数和虚基类 。
非静态成员必须声明为 public 。
类中的第一个非静态成员的类型与其基类不同 ,例如:
class B1{}; class B2 : B1 { B1 b; };class B2 的第一个非静态成员 b 是基类类型 ,所以它不是 POD 类型 。
在类或者结构体继承时 ,满足以下两种情况之一: 派生类中有非静态成员 ,且只有一个仅包含静态成员的基类; 基类有非静态成员 ,而派生类没有非静态成员。比如下面的例子:
class B1 { static int n; }; class B2 : B1 { int n1; }; class B3 : B2 { static int n2; };对于 B2 ,派生类 B2 中有非静态成员 ,且只有一个仅包含静态成员的基类 B1 ,所以它是 POD 类型 。对于 B3 ,基类 B2 有非静态成员 ,而派生类 B3 没有非静态成员 ,所以它也是 POD 类型 。
所有非静态数据成员均和其基类也符合上述规则(递归定义),也就是说 POD 类型不能包含非 POD 类型的数据。
此外 ,所有兼容C语言的数据类型都是 POD 类型(struct 、union 等不能违背上述规则) 。
2. C++11 允许联合体有静态成员
C++11 删除了联合体不允许拥有静态成员的限制 。例如:
union U { static int func() { int n = 3; return n; } };需要注意的是 ,静态成员变量只能在联合体内定义,却不能在联合体外使用 ,这使得该规则很没用 。
3. 非受限联合体的赋值注意事项
C++11 规定 ,如果非受限联合体内有一个非 POD 的成员 ,而该成员拥有自定义的构造函数 ,那么这个非受限联合体的默认构造函数将被编译器删除;其他的特殊成员函数 ,例如默认拷贝构造函数 、拷贝赋值操作符以及析构函数等 ,也将被删除 。这条规则可能导致对象构造失败 ,比如下面的例子:
#include <string> using namespace std; union U { string s; int n; }; int main() { U u; // 构造失败 ,因为 U 的构造函数被删除 return 0; }在上面的例子中 ,因为 string 类拥有自定义的构造函数 ,所以 U 的构造函数被删除;定义 U 的类型变量 u 需要调用默认构造函数 ,所以 u 也就无法定义成功 。
解决上面问题的一般需要用到 placement new ,代码如下:
#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 是什么?
placement new 是 new 关键字的一种进阶用法,既可以在栈(stack)上生成对象 ,也可以在堆(heap)上生成对象 。相对应地 ,我们把常见的 new 的用法称为 operator new ,它只能在 heap 上生成对象 。placement new 的语法格式如下:
new(address) ClassConstruct(...)address 表示已有内存的地址 ,该内存可以在栈上 ,也可以在堆上;ClassConstruct(...) 表示调用类的构造函数 ,如果构造函数没有参数 ,也可以省略括号 。placement new 利用已经申请好的内存来生成对象 ,它不再为对象分配新的内存 ,而是将对象数据放在 address 指定的内存中 。在本例中 ,placement new 使用的是 s 的内存空间 。
4. 非受限联合体的匿名声明和“枚举式类 ”
匿名联合体是指不具名的联合体(也即没有名字的联合体) ,一般定义如下:
union U{ union { int x; }; //此联合体为匿名联合体 };可以看到 ,联合体 U 内定义了一个不具名的联合体,该联合体包含一个 int 类型的成员变量 ,我们称这个联合体为匿名联合体。
同样的 ,非受限联合体也可以匿名,而当非受限的匿名联合体运用于类的声明时 ,这样的类被称为“枚举式类 ” 。示例如下:
#include <cstring> using namespace std; class Student{ public: Student(bool g, int a): gender(g), age(a){} bool gender; int age; }; class Singer { public: enum Type { STUDENT, NATIVE, FOREIGENR }; 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 = FOREIGENR; } ~Singer(){} private: Type t; union { Student s; int id; char name[10]; }; }; int main() { Singer(true, 13); Singer(310217); Singer("J Michael", 9); return 0; }上面的代码中使用了一个匿名非受限联合体 ,它作为类 Singer 的“变长成员 ”来使用 ,这样的变长成员给类的编写带来了更大的灵活性 ,这是 C++98 标准中无法达到的(编译器会报member Student Singer::<anonymous union>::s with constructor not allowed in union错误) 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!