C++11非受限联合体的使用

目录
  • 1. C++11 允许非 POD 类型
  • 2. C++11 允许联合体有静态成员
  • 非受限联合体的赋值注意事项
  • 非受限联合体的匿名声明和“枚举式类”

在 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语言而制定,然而在长期的编程实践中发现,这种规定是没有必要的。
关于 POD 类型稍后我们会讲解,大家先不要着急。

接下来,我们具体看一下 C++11 对 C++98 的改进。

1. C++11 允许非 POD 类型

C++98 不允许联合体的成员是非 POD 类型,但是 C++1 1 取消了这种限制。

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 类型。

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

我们来看具体的例子:

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

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

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

7) 此外,所有兼容C语言的数据类型都是 POD 类型(struct、union 等不能违背上述规则)。

2. C++11 允许联合体有静态成员

C++11 删除了联合体不允许拥有静态成员的限制。例如:

union U {
    static int func() {
        int n = 3;
        return n;
    }
};

需要注意的是,静态成员变量只能在联合体内定义,却不能在联合体外使用,这使得该规则很没用。

非受限联合体的赋值注意事项

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 的内存空间。

非受限联合体的匿名声明和“枚举式类”

匿名联合体是指不具名的联合体(也即没有名字的联合体),一般定义如下:

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非受限联合体的使用的文章就介绍到这了,更多相关C++11非受限联合体内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++联合体转换成C#结构的实现方法

    本文实例主要参考MSDN:http://msdn.microsoft.com/zh-cn/library/ya9bz4ha%28v=vs.80%29.aspx 最近因为项目中遇到了C++联合体转换成C#结构的情况,查了很多资料才转换成功. 备注:对于官方这个UNION示例,我的简单理解,就是这类UNION做法,可适当代替IF ELSE 之类操作. 解决问题:如果只接收视频解码数据,而从不接收音频数据,那么是否可以直接定义结构体,并以此来获取视频数据. /// <summary> /// * @

  • C++联合体union用法实例详解

    本文实例讲述了C++联合体union用法.分享给大家供大家参考.具体如下: 我们应该按照C中的convention去使用union,这是我这篇文章要给出的观点.虽然C++使得我们可以扩展一些新的东西进去,但是,我建议你不要那样去做,看完这篇文章之后,我想你大概也是这么想的. C由于没有类的概念,所有类型其实都可以看作是基本类型的组合,因此在union中包含struct也就是一件很自然的事情了,到了C++之后,既然普遍认为C++中的struct与class基本等价,那么union中是否可以有类成员

  • C++11非受限联合体的使用

    目录 1. C++11 允许非 POD 类型 2. C++11 允许联合体有静态成员 非受限联合体的赋值注意事项 非受限联合体的匿名声明和“枚举式类” 在 C/C++ 中,联合体(Union)是一种构造数据类型.在一个联合体内,我们可以定义多个不同类型的成员,这些成员将会共享同一块内存空间.老版本的 C++ 为了和C语言保持兼容,对联合体的数据成员的类型进行了很大程度的限制,这些限制在今天看来并没有必要,因此 C++11 取消了这些限制. C++11 标准规定,任何非引用类型都可以成为联合体的数

  • 多种型号的ADSL路由设置方法图文教程第1/2页

    中兴831adsl modem 如何设置831:     先要把831的内置软件刷到2.79版本,可www.zte.com.cn下载(如何刷在说明里面) 831出厂设置的以太网口IP地址是192.168.1.1,为了能用telnet访问到831,我们现在先把一台电脑的地址设置为192.168.1.*, 使用ping 192.168.1.1 看看能不能访问到831,没问题的话继续,在DOS模式下打入telnet 192.168.1.1,如果你刚刷好软件,是有密码的,原厂密码是adsl831接着我们

  • Kotlin协程概念原理与使用万字梳理

    目录 一.协程概述 1.概念 2.特点 3.原理 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 三.协程使用 1.协程的启动 2.协程间通信 3.多路复用 4.序列生成器 5.协程异步流 6.全局上下文 一.协程概述 1.概念 协程是Coroutine的中文简称,co表示协同.协作,routine表示程序.协程可以理解为多个互相协作的程序.协程是轻量级的线程,它的轻量体现在启动和切换,协程的启动不需要申请额外的堆栈空间:协程的切换发生在

  • Kotlin协程的基础与使用示例详解

    目录 一.协程概述 1.概念 2.特点 3.原理 1)续体传递 2)状态机 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 1)协程状态的转换 2)状态标识的变化 三.协程使用 1.协程的启动 1)runBlocking方法 2)launch方法 3)async方法 4)suspend关键字 5)withContext方法 6)suspend方法 2.协程间通信 1)Channel 2)Channel的容量 3)produce方法与act

  • 史上最全的PHP正则表达式(手机号需要加上177-***)

    首先看下正则表达式思维导图: 一.校验数字的表达式  1 数字: ^[0-9]*$ 2 n位的数字: ^\d{n}$ 3 至少n位的数字: ^\d{n,}$ 4 m-n位的数字: ^\d{m,n}$ 5 零和非零开头的数字: ^(0|[1-9][0-9]*)$ 6 非零开头的最多带两位小数的数字: ^([1-9][0-9]*)+(.[0-9]{1,2})?$ 7 带1-2位小数的正数或负数: ^(\-)?\d+(\.\d{1,2})?$ 8 正数.负数.和小数: ^(\-|\+)?\d+(\.\

  • jQuery Validate插件实现表单验证

    jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求.该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证,同时提供了一个用来编写用户自定义方法的 API.所有的捆绑方法默认使用英语作为错误信息,且已翻译成其他 37 种语言.更重要的是他是由jQuery 团队. jQuery UI 团队的主要开发人员Jörn Zaefferer 编写和维护的.具体我们可以访问 jQuery Validate 官网,下载

  • 最全的常用正则表达式大全——包括校验数字、字符、一些特殊的需求等

    一.校验数字的表达式 1 数字:^[0-9]*$ 2 n位的数字:^\d{n}$ 3 至少n位的数字:^\d{n,}$ 4 m-n位的数字:^\d{m,n}$ 5 零和非零开头的数字:^(0|[1-9][0-9]*)$ 6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$ 7 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$ 8 正数.负数.和小数:^(\-|\+)?\d+(\.\d+)?$ 9 有两位小数的正实数:^[0-9]

  • 比较全面的C 、Java、JavaScript中的正则表达式详解

    什么是正则表达式? 正则表达式(Regular Expression) 就是用某种模式去匹配一类字符串的公式.如你要在一篇文章中查找第一个字是"罗"最后一个字是"浩"的三个字的姓名,即"罗 * 浩":那么"罗 * 浩"就是公式,也称作 模式(Pattern) ,这篇文章就是 要匹配的串( 或叫文本 text) .再如,你要检查输入的一个字符串是否是 126 邮箱的格式,你得制定一个规则去查检,这种规则就是正则表达式. 从入门开

  • 最全最实用的正则表达式大全分享

    正则表达式,又称正规表示法.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表达式使用单个字符串来描述.匹配一系列符合某个句法规则的字符串.在很多文本编辑器里,正则表达式通常被用来检索.替换那些符合某个模式的文本. 一.校验数字的表达式 1 数字:^[0-9]*$  2 n位的数字:^\d{n}$  3 至少n位的数字:^\d{n,}$  4 m-n位的数字:^\d{m,n}$  5 零和非零开头的数字:^(0|

  • 我的正则

    1 至少8个字符 ".{8,}" 2 URL http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)? URL(2) ^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$ 3 E-Mail \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* E-Mail(2) ^[\w\.-]+@[\w-]+\.[\w\.-]+$ E-Mail(3) ^[\\w-]+(

随机推荐