带限定作用域的枚举类型和非不限定作用域的枚举型别

带限定作用域的枚举型别通过enum class声明,非限定作用域的枚举型别通过enum声明。

1、非限定作用域的枚举型别可能导致枚举量泄漏到所在的作用域空间

namespace TestSpace {
enum Color {
  red = 0,
  green,
  blue,
};
auto red = true;  // 错误
}; // namespace TestSpace


而限定作用域的枚举型别

namespace TestSpace {
enum class Color {
  red = 0,
  green,
  blue,
};
auto red = true; // 没问题

}; // namespace TestSpace

2.非限定作用域的枚举型别存在一些隐式转换

这个我不能说是不好的语法,我的意思是你知道自己代码在干嘛。
比如这段代码

#include <iostream>
namespace TestSpace {
enum Color {
  red = 0,
  green,
  blue,
};
}; // namespace TestSpace

int main() {
  using namespace TestSpace;
  for (int i = 0; i < static_cast<int>(Color::blue); i++) {
    std::cout << i << std::endl;
  }
  // or
  Color color = Color::green;
  if (1 < color < 2) {
    std::cout << color << std::endl;
  }
  return 0;
}

这段代码运行是没问题的。
但是将在num后加上class,就不存在任何隐式转换了。

如果你能控制非限定作用域的枚举型别,做什么那么我觉得enumenum class你喜欢就好。

#include <iostream>
namespace TestSpace {

class Buffer {
public:
  enum Color {
    red = 0,
    green,
    blue,
  };
}; // class Buffer

}; // namespace TestSpace
using namespace TestSpace;
using Color = Buffer::Color;
int main() {
  Color color = Color::green;
  if (Color::red < color < Color::blue) {
    std::cout << color << std::endl;
  }
  return 0;
}

反而这里如果在enum后加上class做判断的时候还要加上强制类型转换。那么就不自然了.

3. C++98不支持enum进行前置声明

enum Color;  // 错误
enum class Coloe; // 正确

但是在c++11后enum也可以前置声明了,但是你必须设置它的底层型别。

举个例子

enum Status {
  good = 0;
  failed = 1;
  incomplete = 100;
  indeterminate = 0xFFFFFFFF;
};

编译器为了表示00xFFFFFFFF,通常会选用一个整数型别来表示Status的取值。
事实上为了节约内存,编译器通常选用能够表示枚举类别取值的最小底层型别。这就说C++98编译器只支持枚举型别定义,可以保证枚举型别被
使用前确定,底层型别是谁。
C++98枚举类型前置声明功能的缺乏可能导致导致编译的依赖性。比如就是简单的增加Status的一个值,可能就需要重新编译整个工程。

enum class Status;
void Process(Status status); // C++98采用前置声明的枚举型别 ,如果Status发生改变,及时没有影响Process的实现
                             // 也需要重新编译整个文件

4. 为什么C++限定作用域枚举就可以很大程度上避免这个问题呢?

其实我们已经知道这个问题的答案了,就是确定枚举型别的底层型别。
C++默认限定作用域的底层型别是int。
当然你可能看到过这样的代码

enum class Status : std::uint32_t; // 这就是指定Status的底层型别

如果看到这里你就明白,为什么c++11不带限定作用域的枚举型别也可以前置声明了。

enum Status : int; // 不带限定作用的枚举型别进行前置声明。

5. 再举个不带限定作用域的优点吧

using UserInfo = std::tuple<std::string, // 姓名
                            std::string, // 电子邮件
                            std::size_t>; // 积分
auto val = std::get<1>(uInfo); // 这里我们用1来获取get uInfo的中的电子邮件地址

但是实际上可能代码迭代过很久后,你也不知道UserInfo中1就是代表电子邮件地址。
这时候我们就可添加一个不带限定作用域的枚举类型来辅助我们记住这个tuple。

using UserInfo = std::tuple<std::string, // 姓名
                            std::string, // 电子邮件
                            std::size_t>; // 积分
enum UserInfoFields {uiName, uiEmail, uiReputation}; // 关联UserInfo,这个有点map的味道
auto val = std::get<uiEmail>(uInfo);

这就是利用不带限定作用域的枚举型别可以进行隐式转换。
如果使用带限定作用域的枚举型别就需要进行强制类型转换。

auto val = std::get<std::static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);

这样写出代码就不太好看。

如果不这么写也是可以的,简单的说就是希望通过传入一个枚举变量得到一个std::size_t,其实就是得到尖括号中的值,
那么我就需要编译时期确定。那么我们就写一个模板函数吧。

template<typename E>
constexpr typename std::underlying_type<E>::type
  toUType(E enumberator) noexcept {
    return static_cast<typename std::underly_type<E>::type>(enumberator);
}

在c++14中就可以写成这样

template<typename E>
constexpr std::underlying_type_t<E>
  toUType(E enumberator) noexcept {
    return static_cast<std::underly_type_t<E>::type>(enumberator);
}

或者加个auto

template<typename E>
constexpr auto
  toUType(E enumberator) noexcept {
    return static_cast<std::underly_type_t<E>::type>(enumberator);
}

那么我们客户段代码就可以写成

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uIndo);

本文全部来自《effect modern c++》

posted @ 2021-01-11 17:41  cyssmile  阅读(779)  评论(0编辑  收藏  举报