跳转到内容

更多 C++ 习语/类型安全枚举

来自维基教科书,开放的书籍,开放的世界

类型安全枚举

[编辑 | 编辑源代码]

提高 C++ 中原生枚举数据类型的类型安全性。

也称为

[编辑 | 编辑源代码]

类型化枚举

C++03 中的枚举类型没有足够的类型安全性,可能会导致意外错误。尽管是语言支持的功能,枚举类型也存在代码移植性问题,因为不同的编译器在处理枚举类型的极端情况时有不同的方式。枚举类型周围的问题可以分为 3 类[1]

  • 隐式转换为整数
  • 无法指定底层类型(在 c++11 之前)[2]
  • 作用域问题

但是,C++03 枚举类型并非没有类型安全性。特别是,不允许将一种枚举类型直接赋值给另一种枚举类型。此外,没有从整数值到枚举类型的隐式转换。然而,大多数意外的枚举类型错误都可以归因于它能够自动提升为整数。例如,考虑以下有效的 C++03 程序。只有少数编译器(如 GNU g++)会发出警告以防止如下意外错误。

enum color { red, green, blue };
enum shape { circle, square, triangle };

color c = red;
bool flag = (c >= triangle); // Unintended!

C++03 枚举类型的另一个问题是它们无法指定用于保存枚举器值的底层表示类型。这种未明确指定的副作用是,底层类型的尺寸和符号性在不同的编译器之间有所不同,这会导致不可移植的代码。最后,C++03 的作用域规则可能会造成不便。例如,同一个作用域中的两个枚举类型不能具有相同名称的枚举器。

类型安全枚举习语是一种解决 C++03 枚举类型问题的方法。注意,C++11 中的 enum class 消除了对这种习语的需求。

解决方案和示例代码

[编辑 | 编辑源代码]

类型安全枚举习语将实际的枚举类型包装在一个类或结构体中,以提供强类型安全性。

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
  typedef inner type;
  inner val;

public:

  safe_enum() {}
  safe_enum(type v) : val(v) {}
  type underlying() const { return val; }

  friend bool operator == (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val == rhs.val; }
  friend bool operator != (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val != rhs.val; }
  friend bool operator <  (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val <  rhs.val; }
  friend bool operator <= (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val <= rhs.val; }
  friend bool operator >  (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val >  rhs.val; }
  friend bool operator >= (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val >= rhs.val; }
};

struct color_def {
  enum type { red, green, blue };
};
typedef safe_enum<color_def> color;

struct shape_def {
  enum type { circle, square, triangle };
};
typedef safe_enum<shape_def, unsigned char> shape; // unsigned char representation

int main(void)
{
  color c = color::red;
  bool flag = (c >= shape::triangle); // Compiler error.
}

在上面的解决方案中,实际的枚举类型被包装在 color_defshape_def 结构体中。要从定义中获取安全的枚举类型,使用 safe_enum 模板。safe_enum 模板使用 参数化基类 习语,即它从 def 参数本身公有继承。结果,在定义中定义的枚举类型在 safe_enum 实例化中可用。

safe_enum 模板还支持一种指定用于保存枚举器值底层类型(inner)的方法。默认情况下,它与定义中的枚举类型相同。使用显式底层表示类型作为第二个类型参数,可以以可移植的方式控制 safe_enum 模板实例化的尺寸。

safe_enum 模板防止自动提升为整数,因为它没有转换运算符。相反,它提供 underlying() 函数,该函数可用于检索枚举器的值。模板还提供重载运算符,以允许对类型安全枚举器的简单比较和完全排序。

枚举类型迭代

在枚举类型全是连续值的情况下,也可以迭代整个枚举类型集。严格来说,迭代不属于该习语的一部分,但它是一个有用的增强功能。以下代码显示了添加迭代功能所需的增强功能。为 safe_enum 类的每个实例化创建了一个 safe_enum 的静态数组。该数组由 initialize 函数使用原始枚举类型值填充。请注意,在 initialize 函数中,假设枚举类型值是连续的。一旦数组初始化,迭代就是从数组的开头遍历到数组的结尾。beginend 函数返回指向数组开头和(最后一个元素之后)的迭代器。

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
  // ... 
  // The stuff shown above goes here.
  // ...
private:
  static safe_enum array[def::_end_ - def::_begin_];
  static bool init;

  // Works only if enumerations are contiguous.
  static void initialize()
  {
    if(!init) // use double checked locking in case of multi-threading.
    {
      unsigned int size = def::_end_ - def::_begin_;
      for(unsigned int i = 0, j = def::_begin_; i < size; ++i, ++j)
        array[i] = static_cast<typename def::type>(j);
      init = true;
    }
  }

public:
  static safe_enum * begin() {
    initialize();
    return array;
  }

  static safe_enum * end() {
    initialize();
    return array + (def::_end_ - def::_begin_);
  }
};

template <typename def, typename inner>
safe_enum<def, inner> safe_enum<def, inner>::array[def::_end_ - def::_begin_];

template <typename def, typename inner>
bool safe_enum<def, inner>::init = false;

template <class Enum>
void f(Enum e)
{
  // ...
}
int main()
{
  typedef safe_enum<color_def, unsigned char> color;
  std::for_each(color::begin(), color::end(), &f<color>);
}

已知用途

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]


参考文献

[编辑 | 编辑源代码]
华夏公益教科书