zoukankan      html  css  js  c++  java
  • C++11 非受限联合体

    【1】受限联合体

    一个联合体内,可以定义多种不同数据类型的成员,这些数据成员将会共享相同内存空间,在一些需要复用内存的情况下,可以达到节省空间的目的。

    不过,根据C++98标准,并不是所有的数据类型都能够成为联合体的数据成员。如下代码:

     1 struct Student
     2 { 
     3     Student(bool g, int a) : gender(g), age(a) {} 
     4     
     5     bool gender;
     6     int age;
     7 };
     8 
     9 union T
    10 {
    11     Student s; // 编译失败,Student不是一个POD类型 
    12     int id; 
    13     char name[10]; 
    14 };

    声明了一个Student的类型。根据之前POD类型的知识,由于Student自定义了一个构造函数,所以该类型是非POD类型的。

    在C++98标准中,union T是无法通过编译的。事实上,除了非POD类型之外,C++98标准也不允许联合体拥有静态或引用类型的成员。

    这样虽然可能在一定程度上保证了和C的兼容性,不过也为联合体的使用带来了很大的限制。

    【2】非受限联合体

    在C++11标准中,取消了联合体对于数据成员类型的限制。

    标准规定,任何非引用类型都可以成为联合体的数据成员,而这样的联合体即所谓非受限联合体(Unrestricted Union)。

    (1)非受限联合体中的静态成员(静态成员变量和静态成员函数)

    联合体拥有静态成员(在非匿名联合体中)的限制,也在C++11新标准中被删除了。

    不过从实践中,发现C++11的规则不允许静态成员变量的存在(否则所有该类型的联合体将共享一个值)。

    而静态成员函数存在的唯一作用,大概就是为了返回一个常数,如下示例:

     1 #include <iostream> 
     2 using namespace std;
     3 
     4 union T
     5 { 
     6     static long Get() { return 2020; }
     7 };
     8 
     9 int main()
    10 { 
    11     cout << T::Get() << endl; // 2020
    12 }

    定义了一个有静态成员函数的联合体,不过看起来这里的union T更像是一个作用域限制符,并没有太大的实用意义。

    (2)非受限联合体的初始化

    C++98标准规定,联合体会自动对未在初始化成员列表中出现的成员赋默认初值。

    然而对于联合体而言,这种初始化常常会带来疑问,因为在任何时刻只有一个成员可以是有效的。如下示例:

    1 union T 
    2 { 
    3     int x;
    4     double d;
    5     char b[sizeof(double)];
    6 }; 
    7 
    8 T t = { 0 }; // 到底是初始化第一个成员还是所有成员呢?

    使用了花括号组成的初始化列表,试图将成员变量x初始化为零,即整个联合体的数据t中低位的4字节被初始化为0,然而实际上,t所占的8个字节将全部被置0。

    而在C++11中,为了减少这样的疑问,标准会默认删除一些非受限联合体的默认函数。

    比如,非受限联合体有一个非POD的成员,而该非POD成员类型拥有非平凡的构造函数,那么非受限联合体成员的默认构造函数将被编译器删除。

    其他的特殊成员函数,例如默认拷贝构造函数、拷贝赋值操作符以及析构函数等,也将遵从此规则。

    如下示例:

     1 #include <string>
     2 using namespace std; 
     3 
     4 union T
     5 { 
     6     string s; // string有非平凡的构造函数 
     7     int n;
     8 }; 
     9 
    10 int main()
    11 { 
    12     T t; // 构造失败,因为T的构造函数被删除; ERROR:尝试引用已删除的函数
    13 }

    联合体T拥有一个非POD的成员变量s。而string却有非平凡的构造函数,因此T的构造函数被删除,其类型的变量t也就无法声明成功。

    解决这个问题的办法:由程序员自己为非受限联合体定义构造函数。通常情况下,placement new会发挥很好的作用,改为如下:

     1 #include <string>
     2 using namespace std; 
     3 
     4 union T
     5 { 
     6     string s; // string有非平凡的构造函数 
     7     int n;
     8 public:
     9     T() { new (&s) string; }  // 自定义构造函数
    10     ~T() { s.~string(); }     // 自定义析构函数
    11 }; 
    12 
    13 int main()
    14 { 
    15     T t; // 编译通过
    16 }

    自定义了union T的构造和析构函数。构造时,采用placement new将s构造在其地址&s上。这里placement new的唯一作用只是调用了一下string的构造函数。

    而在析构时,又调用了string的析构函数。

    必须注意的是,析构的时候union T也必须是一个string对象,否则可能导致析构的错误(或者让析构函数为空,至少不会造成运行时错误)。

    这样一来,变量t的声明就可以通过编译。

    【3】匿名非受限联合体

    匿名非受限联合体可以运用于类的声明中,这样的类也称为“枚举式的类”。如下示例:

     1 #include <string>
     2 #include <iostream>
     3 using namespace std;
     4 
     5 struct Student
     6 {
     7     Student(bool g, int a) : gender(g), age(a) {}
     8 
     9     bool gender;
    10     int age;
    11 };
    12 
    13 class Singer
    14 {
    15 public:
    16     enum Type { STUDENT, NATIVE, FOREIGNER };
    17     Singer(bool g, int a) : s(g, a)
    18     {
    19         t = STUDENT;
    20     }
    21     Singer(int i) : id(i) 
    22     {
    23         t = NATIVE;
    24     }
    25     Singer(const char* n, int s)
    26     {
    27         int size = (s > 9) ? 9 : s;
    28         memcpy(name, n, size);
    29         name[s] = '';
    30         t = FOREIGNER;
    31     }
    32     ~Singer() {}
    33     void print()
    34     {
    35         switch (t)
    36         {
    37         case STUDENT:
    38             cout << "s.gender: " << s.gender << endl;
    39             cout << "s.age: " << s.age << endl;
    40             break;
    41         case NATIVE:
    42             cout << "id: " << id << endl;
    43             break;
    44         case FOREIGNER:
    45             cout << "name: " << name << endl;
    46             break;
    47         default:
    48             break;
    49         }
    50     }
    51 private:
    52     Type t;
    53     union
    54     {
    55         Student s;
    56         int id;
    57         char name[10];
    58     };
    59 };
    60 
    61 int main()
    62 {
    63     Singer objSer1(true, 13);
    64     objSer1.print();
    65 
    66     Singer objSer2(20200129);
    67     objSer2.print();
    68 
    69     Singer objSer3("kaizenliu", 9);
    70     objSer3.print();
    71 
    72     system("pause");
    73 }
    74 
    75 /*运行结果
    76 s.gender: 1
    77 s.age: 13
    78 id: 20200129
    79 name: kaizenliu
    80 */

     把匿名非受限联合体成为类Singer的“变长成员”(variant member)。可以看到,这样的变长成员给类的编写带来了更大的灵活性。

     

    good good study, day day up.

    顺序 选择 循环 总结

  • 相关阅读:
    一段路
    memcache 键名的命名规则以及和memcached的区别
    浏览器解释网页时乱码
    windows下安装Apache
    巧用PHP数组函数
    程序返回值的数据结构
    Linux如何生成列表
    判断用户密码是否在警告期内(学习练习)
    判断用户的用户名和其基本组的组名是否一致
    sed笔记
  • 原文地址:https://www.cnblogs.com/Braveliu/p/12240487.html
Copyright © 2011-2022 走看看