跳轉到內容

更多 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>);
}

已知用途

[編輯 | 編輯原始碼]
[編輯 | 編輯原始碼]


參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書