C++枚舉

1. 介紹

第一次學習枚舉類型時,以爲這個名字很詭異。可是後來發現,「枚舉」真的特別傳神,枚舉就是可數的意思。html

當你發現某個類型的值是數得過來的,那就派枚舉出場吧。c++


2. C++11前的枚舉

C++11是個大版本,必定程度上從新定義了C++,其中就包括新增的emum class。追本溯源,咱們先看老枚舉。程序員

enum Color { WHITE=1, BLACK=2 };

名字衝突

若是咱們的代碼定義了兩個不一樣的枚舉類型,而它們的枚舉值取了相同的名字,那麼編譯器將報錯。編程

enum Color { WHITE=1, BLACK=2 };

enum Species { BLACK=1, WHITE=2, YELLOW=3 };

這種錯誤在如下狀況很容易發生安全

  • 多人協做,員工A還來不及知道員工B是怎麼定義Color的
  • 與庫衝突,若是Color是在庫中定義的,程序員更有可能如此定義

因而公司爲了減小此類錯誤,可能制定以下編程規範:函數

全部的枚舉,值的名字都要以該枚舉名爲前綴工具

因而Species定義以下,是否是略感繁瑣呢?佈局

enum Species {
     SPECIES_BLACK = 1,
     SPECIES_WHITE = 2,
     SPECIES_YELLOW = 3
};

類型不安全

名字衝突會在編譯期暴露,可是類型問題卻會躲過編譯器的審查,致使難以追蹤的邏輯Bug,更爲可怕。老式枚舉很大程度上只是個整數類型。學習

枚舉能夠直接和數字比較指針

enum Color { COLOR_WHITE=1, COLOR_BLACK=2 };

if (COLOR_WHITE == 1) {
    // do something
}

不一樣的枚舉也能夠比較

enum Color { COLOR_WHITE=1, COLOR_BLACK=2 };

enum Species {
    SPECIES_BLACK=1,
    SPECIES_WHITE=2,
    SPECIES_YELLOW=3
};

if (COLOR_WHITE == SPECIES_BLACK) {
    // do something
}

不要覺得程序員不會犯這樣的錯誤,當被產品經理的需求踢着屁股跑時,他們可什麼事情都幹得出來。

不能前向聲明

前向聲明是隱藏實現和減小代碼依賴的有利工具。可是下面的代碼會編譯報錯。

// forward declaration
enum Color;

class A {
public:
    void foo(Color c);
private:
    // some members ..
};

C++的編譯過程實際上是內存佈局的過程。當咱們前向聲明class時,會使用指針或引用,內存大小固定是8個字節(64位機器)。但這裏使用的是Color自己,而枚舉的大小實際上是由實現決定的。

enum Color { COLOR_WHITE = 1, COLOR_BLACK = 2 };
// sizeof(Color) == 4

enum Color { COLOR_WHITE = 100000000000, COLOR_BLACK = 2 };
// sizeof(Color) == 8

看到沒有,通常狀況下枚舉的大小是4字節,當枚舉值太大時,就會變成8字節。因此編譯器並不能僅靠前向聲明下判斷。


3. C++11的枚舉

C++11提出了enum class,也稱爲strong typed enum,它解決了老式枚舉的三個問題

名字衝突

enum class Color { WHITE=1, BLACK=2 };
enum class Species { BLACK=1. WHITE=2, YELLOW=3, BROWN=4 };

// 使用時,枚舉類型名爲上層命名空間
// Color::WHITE
// Color::BLACK
// Species::BLACK
// Species::YELLOW

類型安全

枚舉不能直接和數字比較

// 編譯報錯
if (Color::WHITE == 1) {
    // do something
}

不一樣的枚舉不能比較

// 編譯報錯
if (Color::WHITE == Species::WHITE) {
    // do something
}

前向聲明

enum class 能夠指定底層的實現,因此編譯器就知道枚舉的大小了。

// 成功編譯,哈哈
// 還能夠指定 int, unsigned int, short等,整數類型均可以
enum class Color : char;

class A {
public:
    void foo(Color c);
};

enum class Color : char { WHITE=1, BLACK=2 };
// sizeof(Color) == 1

4. C++11 提醒

雖然enum class作了不少改進,可是並不完美,仍是有不少值得注意的地方。

enum class不是類

新枚舉不是class,而是一個單獨的類型,更像是整數類型的封裝。它沒有像類同樣的構造和析構機制。若是定義一個未指定值的枚舉,那麼默認值是0,即便定義中沒有0

enum class Color { WHITE=1, BLACK=2 };
Color c;

static_cast<int>(c); // it's 0

enum class不能定義方法

這是我怨念的地方。有時候我想把枚舉以字符串的形式打印出來,可讀性更好。若是能像下面同樣寫代碼多好,惋惜enum class不支持定義任何方法。

Color c = Color::WHITE
// 不可能如此調用
c.toString()  // "white"

因而只能用單獨的函數實現了,代碼的組織不夠緊湊,也沒夠美觀。

const char* ColorToString(Color c)
{
    switch (c) {
      case Color::WHITE:
        return "white";
      case Color::BLACK:
        return "black";
      default:
        abort();    // 多是未初始化的枚舉,這屬於邏輯錯誤
    }
}

5. 參考資料

  1. Bjarne Stroustrup's C++11 FAQ
  2. Better types in C++11 - nullptr, enum classes (strongly typed enumerations) and cstdint
  3. StackOverflow: Why is enum class preferred over plain enum?
相關文章
相關標籤/搜索