首页 > 编程语言 >C++11:非受限联合体(union)

C++11:非受限联合体(union)

时间:2022-12-28 10:58:52浏览次数:38  
标签:11 union 成员 联合体 C++ int POD 构造函数

在 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等):

  1. 没有用户自定义的构造函数、析构函数、拷贝构造函数和移动构造函数。

  2. 不能包含虚函数和虚基类。

  3. 非静态成员必须声明为 public。

  4. 类中的第一个非静态成员的类型与其基类不同,例如:

class B1{};
class B2 : B1 { B1 b; };

class B2 的第一个非静态成员 b 是基类类型,所以它不是 POD 类型。

  1. 在类或者结构体继承时,满足以下两种情况之一:
  • 派生类中有非静态成员,且只有一个仅包含静态成员的基类;
  • 基类有非静态成员,而派生类没有非静态成员。

比如下面的例子:

class B1 { static int n; };
class B2 : B1 { int n1; };
class B3 : B2 { static int n2; };

对于 B2,派生类 B2 中有非静态成员,且只有一个仅包含静态成员的基类 B1,所以它是 POD 类型。对于 B3,基类 B2 有非静态成员,而派生类 B3 没有非静态成员,所以它也是 POD 类型。

  1. 所有非静态数据成员均和其基类也符合上述规则(递归定义),也就是说 POD 类型不能包含非 POD 类型的数据。

  2. 此外,所有兼容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错误)。

标签:11,union,成员,联合体,C++,int,POD,构造函数
From: https://www.cnblogs.com/crossoverpptx/p/17009599.html

相关文章

  • 华大电子MCU-CIU32F011x3、CIU32F031x5低功耗介绍
    9.低功耗(LOWPOWER)9.1.低功耗模式在系统或电源复位以后,微控制器处于正常模式运行状态,系统所用时钟为256KHz内部RC振荡器输出。当CPU不需继续运行时,可以利用进......
  • 洛谷 P5363 / LOJ #3114 「SDOI2019」移动金币
    洛谷传送门LOJ传送门不错的博弈+计数。不难发现题中的游戏是阶梯Nim的变体。若设\(a_i\)为第\(i\)枚金币的位置,令\(\foralli\in[2,m],\b_i=a_i-a_{i-......
  • Effective C++ 电子书 pdf
    EffectiveC++是世界顶级C++大师 ScottMeyers的成名之作,书中的50条准则,每一条都扼要说明了一个可让你写出更好的C++程序代码的方法,并以特别设计过的例子详加讨论。......
  • CF363B 1100
    题意解析前缀和水题,就是在所有长度为k的区间里找个最小的。代码#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=2e5+10,M=......
  • CF368B 1100
    题意解析考察前缀知识。做的时候我好蠢,一直在想怎么前缀,大范围减小范围,怎么消除不同数字是否重复出现的影响。后来发现是自己蠢了,不就是求个后缀么。代码#include<bi......
  • CF313B 1100 *
    题意解析前缀和思想。f[i]用来代表到第i+1位有多少个满足s[k]==s[k+1]所以题目求从l~r,即求f[r-1]-f[l-1]代码#include<bits/stdc++.h>usingnamespacestd;ty......
  • 预告!10 万字的 C++ 八股文 PDF
    大家好,我是小贺。北京最热的7月已经过去了,8月要来了。 晚餐过后散散步,走在小区旁边的小河边,凉风吹在脸上的感觉,就很舒服。每到周末的晚上,自己做做饭,小区周围散散步,吹吹......
  • AcWing. 1165.单词环
    传送门解题思路\(\qquad\)我们一开始可以得出一个建图的思路,对于每个字符串我们把它们当成图中的一个点,然后能“接龙”的字符串之间连一条边,在这张图上跑\(01\)分数规划......
  • 我是如何学好C++的?
    搞音视频开发,免不了要跟 C++打交道。不过,C++的复杂、难学也是出了名的。随着标准版本的演进,C++里包含的东西也越来越多。不断膨胀的核心语言加上庞大的标准库,让学习、使......
  • 都2022年你还不会安装系统?看我三分钟完事PE制作并进行Win11系统安装实践
    本章目录:1.PE启用盘的下载制作2.Win11镜像的下载3.进PE安装Windows11(敲黑板)4.Win11初始化操作都2022年你还不会安装系统?看我三分钟完事PE制作并进行Win11系统安装实践前置......