草庐IT

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

crossoverpptx 2023-03-28 原文

在 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错误)。

有关C++11:非受限联合体(union)的更多相关文章

  1. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  2. 【华为OD技术面试 | 真八股 】MySQL联合索引,谈springIOC的理解,谈springAOP的理解,Erika和zookeeper等问题 - 2

    文章目录华为OD面试流程1.mysql数据库建了两个字段,且设置了联合索引,如果其中有一个字段为空会出现什么问题?2.谈谈springIOC的理解,有什么好处,解决了什么问题3.谈谈springAOP的理解,切面编程有没有实际应用,有哪些注解,作用是什么,有那些应用场景?4.Erika和zookeeper有了解过吗,作用是什么,主要解决了什么问题5.谈谈JDK、JRE、JVM的理解,区别是什么6.谈谈对泛型的理解7.JVM的组成华为OD面试流程机试:三道算法题,关于机试,橡皮擦已经准备好了各语言专栏,可以直接订阅。性格测试:机试技术一面(本专栏核心)技术二面(本专栏核心)主管面试定级定薪发of

  3. ruby - 如何从两个哈希数组中获取联合/交叉/差异并忽略一些键 - 2

    我想从两个哈希数组中获取并集/交集/差集,例如:array1=[{:name=>'Guy1',:age=>45},{:name=>'Guy2',:age=>45}]array2=[{:name=>'Guy1',:age=>45},{:name=>'Guy3',:age=>45}]...parray1-array2=>[{:name=>"Guy2",:age=>45}]parray2-array1=>[{:name=>"Guy3",:age=>45}]parray1|array2=>[{:name=>"Guy1",:age=>45},{:name=>"Guy2",:age=>45},{:

  4. 相机内参标定,相机和激光雷达联合标定 - 2

    相机内参标定,相机和激光雷达联合标定一、相机标定原理1.1成像过程1.2标定详解二、相机和激光雷达联合标定2.1标定方法汇总2.2Autoware的安装与运行2.2.1安装方式2.2.2安装Autoware的依赖(Ubuntu16.04/kinetic)2.2.3编译Autoware1.创造工作空间2.下载Autoware源码3.其他依赖4.编译5.效果2.3Autoware标定激光雷达和相机的外参过程一、相机标定原理1.1成像过程现实物体在相机中的成像过程离不开世界坐标系、相机坐标系、图像坐标系以及像素坐标系,只有理解了这些才能对获取的图像进行准确的分析。成像过程:四个坐标系如下图所示:世界

  5. sql - rails union hack,如何将两个不同的查询放在一起 - 2

    我有一个查询,它在同一个表中搜索两个单独的字段...寻找最有可能是特定城市但也可能是国家的位置...即需要两个字段。表格看起来像:CountryCityGermanyAachenUSAAmarilloUSAAustin结果:KeywordSideinfoAachenGermanyUSACountryAustinUSAGermanyCountry基本上我想知道是否有更简洁的方法来执行此操作,因为我必须使用两个单独的查询,然后将它们加在一起,对它们进行排序等(效果很好):defself.ajax(search)countries=Location.find(:all,:select=>'c

  6. ruby-on-rails - Rails 中的命名空间模型 : What's the state of the union? - 2

    从一开始,Rails就存在命名空间模型的问题。随着时间的推移,几乎每个人都放弃了使用它。包括我自己。随着Rails2.3的发布,我想了解最新情况。我想到的具体问题是:首先,可以出发了吗?表的命名,有什么规律可循?协会,如何以最不冗长的方式声明它们?如何命名外键列?自动请求,如果将模型文件放在与命名空间匹配的子目录中,它会起作用吗?或者,如何命名和放置文件?代,模型生成器是否成功并正确地处理命名空间?生成器,包含Controller的脚手架生成器怎么样?任何应该注意的不兼容性/怪癖? 最佳答案 我见过的关于这个问题的最好的文章来自St

  7. ruby-on-rails - 如何组合重叠的时间范围(时间范围联合) - 2

    我有一个包含多个时间范围的数组:[Tue,24May201108:00:00CEST+02:00..Tue,24May201113:00:00CEST+02:00,Tue,24May201116:30:00CEST+02:00..Tue,24May201118:00:00CEST+02:00,Tue,24May201108:00:00CEST+02:00..Tue,24May201109:00:00CEST+02:00,Tue,24May201115:30:00CEST+02:00..Tue,24May201118:00:00CEST+02:00]我想获得具有重叠时间范围组合的相同数组

  8. ruby-on-rails - 数组合并(联合) - 2

    我有两个数组需要合并,使用Union(|)运算符非常慢..还有其他方法可以完成数组合并吗?此外,数组中填充的是对象,而不是字符串。数组中对象的示例#events.waikato.ac其中source是一小段XML。编辑对不起!“合并”是指我不需要插入重复项。A=>[1,2,3,4,5]B=>[3,4,5,6,7]A.magic_merge(B)#=>[1,2,3,4,5,6,7]了解整数实际上是Article对象,并且Union运算符似乎永远 最佳答案 这是一个对两种合并技术进行基准测试的脚本:使用管道运算符(a1|a2)和使用co

  9. Unity基础知识之顶点吸附、创建组合体 - 2

    Unity基础知识之顶点吸附、创建组合体一、顶点吸附顶点吸附:选择物体后按住键盘上的V键,鼠标定点定位,再拖拽到目标物体对齐即可。注:操作成功后先松V键。1、两个平面Plane的顶点吸附2、两个物体cube的顶点吸附二、创建组合体(子弹)组合体子弹由2个capsule(胶囊)、1个cylinder(圆柱体)组成,如图先创建这3个对象。再将其中一个capsule按照一定比例缩小,将三个对象按照一定位置放置好。创建一个GameObject,将三个对象放在该GameObject里,这样就是父子结构。为创建的组合体即子弹可以添加材质Material:在assets目录下新建Material,选择颜色后

  10. javascript - 在此受限设置中,eval() 会导致安全问题吗? - 2

    我正在编写一个包含一些数学练习的网站。我不太在乎用户是否试图作弊,所以我正在通过Javascript更正答案在我的具体情况下,我在表单中有一个字段。我想让用户输入一个数学表达式(例如3/2)并使用其结果来判断用户是否正确。为此,我会使用eval。我的javascript永远不会直接从URL中读取,而只是从表单中读取。此页面的任何结果都不会存储以显示给任何用户(也许我们稍后会保留统计分析的结果,然后通过PHP存储在数据库中,但话又说回来,我可能需要清理PHP本身的任何输入,怕用户直接使用POST)有什么可能出错?=P 最佳答案 您需要

随机推荐