C++11的enum class & enum struct和enum
C++11的enum class & enum struct和enum
C++标准文档——n2347(学习笔记)
链接:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf
1. 旧版enum存在的问题
| 问题 | 描述 |
|---|---|
| 1 | 向整形的隐式转换(Implicit conversion to an integer) |
| 2 | 无法指定底层所使用的数据类型(Inability to specify underlying type) |
| 3 | enum的作用域(Scope) |
| 4 | 不同编译器解决该问题的方法不统一 |
1.1 问题1:向整形的隐式转换
在开始这个问题之前,我们需要知道什么是整形提升
查看之前的博文:C\C++中的整形提升
在看完什么是整形提升之后,我们开始这个问题:
旧版enum其实并不具有非常完全的类型安全(当然它也体现了一定的类型安全:1.禁止不同枚举体之间的赋值 2.禁止整形向枚举体的隐式转换等),也就是面对整形提升,旧版enum是没有抗拒力的。
例如:
#include <iostream>
enum colorA{redA,greenA,grayA};
enum colorB {redB,greenB,yellowB};
void test(int data) {
std::cout << "test called" << std::endl;
}
int main() {
colorA ca(redA);
colorB cb(greenB);
//ca = cb; ERROR , 无法从“colorB”转换为“colorA”
//ca = 2; ERROR , 无法从“int”转换为“colorA”
bool res(ca < cb); //OK
std::cout << std::boolalpha << res << std::endl;
test(ca); //OK
std::cin.get();
return 0;
}
运行结果:
true
test called
就像上面的代码:我们仍然可以比较两个不同枚举体的大小,用枚举体调用参数为int的函数。显然此时的枚举体发生了 整形提升 。
在无法使用C++11新版enum的情况下,机制的程序员想到了:将enum封装到类的内部的方法。
#include <iostream>
class Color {
private:
enum _color { _red, _blue, _yellow, _black };
public:
explicit Color(const _color & other) {
value = value;
}
explicit Color(const Color & other) {
value = other.value;
}
const Color& operator=(const Color& other) {
value = other.value;
return *this;
}
static const Color red, blue, yellow, black;
_color value;
//operators
bool operator <(const Color & other) { return value < other.value; }
bool operator >(const Color & other) { return value > other.value; }
bool operator <=(const Color & other) { return value <= other.value; }
bool operator >=(const Color & other) { return value >= other.value; }
bool operator ==(const Color & other) { return value == other.value; }
//...
//conversion
int toint() { return value; }
};
//init static const Color obj
const Color Color::red(Color::_color::_red);
const Color Color::blue(Color::_color::_blue);
const Color Color::yellow(Color::_color::_yellow);
const Color Color::black(Color::_color::_black);
void test(int data) {
std::cout << "called" << std::endl;
}
int main() {
Color ca(Color::blue);
std::cout << ca.toint() << std::endl;
//ca = 2; ERROR, 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)
//test(ca); ERROR, 无法将参数 1 从“Color”转换为“int”
//bool res(ca > 2); ERROR,没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)
std::cin.get();
return 0;
}
的确,封装在类中的enum能够抵抗整形提升。但是这种enum不同于POD(plain old data),无法放入寄存器当中,这会带来额外的开销。
1.2 问题2:无法指定底层所使用的数据类型
A. 首先,无法指定数据类型,导致我们无法明确枚举类型所占的内存大小。这种麻烦在结构体当中尤为突出,特别是当我们需要内存对齐和填充处理的时候。
#include <iostream>
enum Version { Ver1 = 1, Ver2, Ver3 };
struct MyStruct {
MyStruct(Version ver) { this->Ver = ver; }
Version Ver;
//Ohters...
};
int main() {
MyStruct m(Version::Ver1);
std::cin.get();
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
此时我们的解决办法还是:不使用enum
#include <iostream>
enum Version { Ver1 = 1, Ver2, Ver3 };
struct MyStruct {
MyStruct(Version ver) { this->Ver = ver; }
unsigned char Ver;//将enum Version转为unsigned char类型
//Ohters...
};
int main() {
MyStruct m(Version::Ver1);
std::cin.get();
return 0;
}
B. 其次,当我们使用enum时,我们无法决定编译器底层是如何对待enum的(比如:signed和unsigned)。
#include <iostream>
enum MyEnum { num1 = 1, num2 = 2, numn = 0xFFFFFF00U };
int main() {
std::cout << num1 << std::endl;
std::cout << num2 << std::endl;
std::cout << numn << std::endl;
std::cin.get();
return 0;
}
VS2015运行结果:
1
2
-256
CodeBlocks运行结果:
1
2
4294967040
在 numn=0xFFFFFF00U;中,我们希望0xFFFFFF00表现为unsigned。但是不同的编译器其标准也不同。这就给我们的程序带来了许多的不确定性。
在文档n2347中的例子:不同编译器对0xFFFFFFF0U的表现。
#include <iostream>
using namespace std;
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };
int main() {
cout << sizeof(E) << endl;
cout << "Ebig = " << Ebig << endl;
cout << "E1 ? -1 =\t" << (E1 < -1 ? "less" : E1 > -1 ? "greater" : "equal") << endl;
cout << "Ebig ? -1 =\t" << (Ebig < -1 ? "less" : Ebig > -1 ? "greater" : "equal") << endl;
}
1.3 问题3:enum的作用域
enum的中的 ” { } ” 大括号并没有将枚举成员的可见域限制在大括号内,导致enum成员曝露到了上一级作用域(块语句)中。
例如:
#include <iostream>
enum color{red,blue};//定义拥有两个成员的enum,red和blue在enum的大括号外部可以直接访问,而不需要使用域运算符。
int main() {
std::cout << blue << std::endl;
std::cin.get();
return 0;
}
运行结果:
1
-
就如上面的代码,我们可以在blue的大括号之外访问它,color的成员被泄露到了该文件的全局作用域中(虽然它尚不具备外部链接性)。可以直接访问,而不需要域运算符的帮助。
但是这不是关键,有时我们反而觉得非常方便。下面才是问题所在:
- 问题:无法定义同名的枚举成员
enum color { red, blue };
//enum MyEnum { red, yellow }; ERROR, 重定义;以前的定义是“枚举数”
如上面的代码所示:我们无法重复使用red这个标识符。因为它在color中已经被用过了。但是,它们明明就是不同的枚举类型,如果可以使用相同的成员名称,然后通过域运算符来访问的话,该有多好!就像下面这样:
color::red但是这是旧版的enum无法做到的。
- 解决上述问题:利用命名空间
#include <iostream>
namespace spaceA {
enum color { red, blue };
}
namespace spaceB {
enum colorX { red, blue };
}
int main() {
std::cout << spaceA::red << std::endl;
std::cout << spaceB::blue << std::endl;
std::cout << std::boolalpha << (spaceA::red > spaceB::blue) << std::endl;
std::cin.get();
return 0;
}
运行结果:
0
1
false
-
是的,只要利用命名空间我们就能解决枚举体的成员重定义问题,但是添加了多余的一层命名空间,未免显得麻烦
1.4 不同编译器解决该问题的方法不统一
在1.2中展示的图片告诉我们:有些编译器可能提供了相应的扩展来解决这些问题,但是有的编译器却没有,这使得我们的编程非常的不统一,有时候因为enum而削弱了程序的可移植性。
2. enum class 和 enum struct
2.1 enum class 和 enum struct 是等价的
2.2 声明
如大标题,枚举体的声明和定义使用 enum class或是enum struct, 二者是等价的。使用enum class\enum struct不会与现存的enum关键词冲突。而且enum class\enum struct具有更好的类型安全和类似封装的特性(scoped nature)。
enum class color{red,green,yellow};enum class colorx{red,green=100,yellow};//....
2.3 类型转换
与整形之间不会发生隐式类型转换,但是可以强转。
#include <iostream>
enum class color { red, green, yellow};
int main() {
//int res(color::red); //ERROR , “初始化”: 无法从“color”转换为“int”
//color col(2);//ERROR , “初始化”: 无法从“int”转换为“color”
//强转
int res(static_cast<int>(color::red));//OK
color col(static_cast<color>(1));//OK
std::cin.get();
return 0;
}
2.4 指定底层数据类型(underlying type)
默认的底层数据类型是int,用户可以通过:type(冒号+类型)来指定任何整形(除了wchar_t)作为底层数据类型。
enum class color:unsigned char{red,blue};
enum calss colorb:long long{yellow,black};
2.5 域
引入了域,要通过域运算符访问,不可以直接通过枚举体成员名来访问(所以我们可以定义相同的枚举体成员而不会发生重定义的错误)
#include <iostream>
enum class color { red, green, yellow};
enum class colorX { red, green, yellow };
int main() {
//使用域运算符访问枚举体成员,强转后打印
std::cout << static_cast<int>(color::red) << std::endl;
std::cout << static_cast<int>(colorX::red) << std::endl;
std::cin.get();
return 0;
}
运行结果:
0
0
3. C++11enum的一些新特点
- 枚举体的定义和声明问题
- 用enum定义的枚举体是一个不具有封装性(不知道如何翻译是好:unscoped enumeration)的枚举体,他的成员可以在enum的大括号外被直接访问。而用enum class或是enum struct(二者在语法上是等价的)定义的枚举体是具有封装性的(scoped enumeration),他的成员同过成员名直接访问,而应通过域运算符来访问。
#include <iostream>
enum class color{red,black};
enum colorx{green,yellow};
int main() {
color::red;//用域运算符访问color的成员
green;//直接访问colorx的成员
colorx::green;//用域运算符访问colorx的成员
std::cin.get();
return 0;
}
- 图中的enum-base应该只能是整形的数据(不能是浮点类型或是其他类型),const或是volatile会被忽略。
- enumerator-list中的成员被作为常量使用,与常量的功能等价。
- 使用=给成员初始化的时候,=右边应该使用常量,这个常量应该为整形或是其他的枚举类型。如果第一个枚举成员没有初始化,那么他默认为0,其他没有初始化的成员是前面一个成员的值加1。
enum color { red=3, black, gray };//成员的值分别为:3 4 5
enum colorx { green, yellow };//成员的值分别为:0 1
enum colorxx{xred,xyellow,xblack=12,xgray};//成员的值分别为:0 1 12 131
- 每种枚举体的类型都不同于其他枚举体。
enum colora{red};//colora的类型与colorb的类型不同
enum colorb{yellow};
每种枚举都具有底层数据类型,同过:type(冒号+类型)来指定。对于指定了数据类型的枚举体,他的数据类型为指定的数据类型。如果没有固定的底层数据类型:
- 对于enum class和enum struct来说,他的底层数据类型是int。
- 对于enum来说,他的底层数据类型根据编译器而不同。
- 如果有使用数据初始化,那么他的数据类型与用来初始化的数据的类型相同。
如果该枚举体没有指定的底层数据类型,而且该枚举体的成员为空,那么这个枚举体相当于只有一个成员0
- enum(非enum class\enum struct)会发生整形提升
#include <iostream>
enum color { red, green, yellow };
int main() {
std::cout << std::boolalpha << (red == green) << std::endl;//(red == green)发生了整形提升
std::cin.get();
return 0;
}
- enum(非enum class\enum struct)会发生自动数据类型转换。但是enum class\enum struct是不允许这么做的。
#include <iostream>
enum color { red, green, yellow };
int main() {
//color col = 2;//ERROR , “初始化”: 无法从“int”转换为“color”
int i = green;//发生隐式转换
std::cout << i << std::endl;
std::cin.get();
return 0;
}
运行结果:
1
- 可以对enum和enum class\enum struct进行强制转换。
#include <iostream>
enum class color { red, green, yellow,a,b,v };
int main() {
int res(0);
//res = color::red + color::green;//ERROR , “color”不定义该运算符或到预定义运算符可接收的类型的转换
res = static_cast<int>(color::red) + static_cast<int>(color::green);
std::cout << res << std::endl;
std::cin.get();
return 0;
}
运行结果:
1
3. 转载请注明出处
- 顶
C++11的enum class & enum struct和enum的更多相关文章
- [转]C++11的enum class & enum struct和enum
1. 旧版enum存在的问题 问题 描述 1 向整形的隐式转换(Implicit conversion to an integer) 2 无法指定底层所使用的数据类型(Inability to spe ...
- 【转】C++11的enum class & enum struct和enum
转自:https://blog.csdn.net/sanoseiichirou/article/details/50180533 C++标准文档——n2347(学习笔记) 链接:http://www. ...
- 关于C与C++的struct,union,enum用法差异
对着代码说话: #include <stdio.h> #include <stdlib.h> struct test { int abc; }; enum _enum {A,B ...
- namespace、struct、enum、union、string(day01)
一 C++概述 C++历史背景 )C++的江湖地位 jave C C++ C# python )C++之父:Bjarne Stroustrup(--) ,Cpre,为C语言增加类的机制 ,Bjarne ...
- C# struct and enum
struct Person { public int age; public string name; public string fname; public string class; } enum ...
- C# Convert an enum to other type of enum
Sometimes, if we want to do convert between two enums, firstly, we may think about one way: var myGe ...
- Go学习笔记01-语言
1.1 变量 Go 是静态类型语言,不能在运行期改变变量类型.使用关键字 var 定义变量,自动初始化为零值.如果提供初始化值,可省略变量类型,由编译器自动推断. var x int var f fl ...
- C++ Core Guidelines
C++ Core Guidelines September 9, 2015 Editors: Bjarne Stroustrup Herb Sutter This document is a very ...
- C++与C的区别一
1. C++风格数组初始化: #include <iostream> #include <array> using namespace std; void main() { / ...
随机推荐
- python版本升级
python 2.7.11,下载链接 https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tgz,如下载速度太慢可在豆瓣pypi搜索下载ht ...
- 解决UDT中内存下不去的问题
使用UDT库,编写简单的网络通信程序,发现了一个问题,关闭一部分连接后,程序占用内存并没有变化. 比如先连接500个,再连接另500个,先关掉后面500个,程序占用内存降一半,再关 ...
- 萌新笔记——C++里将string类字符串(utf-8编码)分解成单个字(可中英混输)
最近在建词典,使用Trie字典树,需要把字符串分解成单个字.由于传入的字符串中可能包含中文或者英文,它们的字节数并不相同.一开始天真地认为中文就是两个字节,于是很happy地直接判断当前位置的字符的A ...
- ipv4理论知识3-分类编址之两级编址
两级编制 分类编址时,同一网络中的所有地址都属于统一个地址块,而每个地址块包含两部分:网络标识和主机标识.网络标识指明了网络,主机标识则指明了连接到该网络的一台特定的主机.如下图描绘了分类编址中一个I ...
- Silicon Labs
Silicon Labs(美国芯科实验室)总部位于美国德克萨斯州的奥斯汀,成立于1996年,拥有全球化的运营.销售和研发团队,是一家业界领先的高性能混合信号IC供应商,为业界提供易用的高度集成化产品方 ...
- redis 源码阅读 数值转字符 longlong2str
redis 在底层中会把long long转成string 再做存储. 主个功能是在sds模块里. 下面两函数是把long long 转成 char 和 unsiged long long 转成 ...
- django url路径与模板中样式相对路径的问题
static目录下有css和js及image等文件夹,里面放置网站的一些静态文件,static位于网站根目录下,django中配置静态文件这个就细说,网上都有,昨天在添加新内容时发现一个问题,我的ur ...
- 理解 OpenStack 高可用(HA)(2):Neutron L3 Agent HA 之 虚拟路由冗余协议(VRRP)
本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...
- JS入门学习,写一个简单的选项卡
/* 经过昨天一整天的纠结和摸索.总结下学习初期我最致命的几个问题…… 1.var oDiv = document.getElementById(''); 一定要多输,熟悉后o u什么的字母别搞 ...
- [bzoj1911][Apio2010特别行动队] (动态规划+斜率优化)
Description Input Output Sample Input - - Sample Output HINT Solution 斜率优化动态规划 首先易得出这样的一个朴素状态转移方程 f[ ...