2013-10-09 215 views
11

我正在尋找使用一組位標誌爲我當前的問題。這些標誌(很好地)被定義爲enum的一部分,但是我明白,當您從枚舉中得到兩個值時,OR操作的返回類型的類型爲int類型安全枚舉位標誌

什麼目前我正在尋找的是一個解決方案,這將使該位掩碼的用戶保持類型安全,因此我已經創造了operator |

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    THREE = 0x04, 
    FOUR = 0x08, 
    FIVE = 0x10, 
    SIX  = 0x20 
}; 

ENUM operator | (ENUM lhs, ENUM rhs) 
{ 
    // Cast to int first otherwise we'll just end up recursing 
    return static_cast<ENUM>(static_cast<int>(lhs) | static_cast<int>(rhs)); 
} 

void enumTest(ENUM v) 
{ 
} 

int main(int argc, char **argv) 
{ 
    // Valid calls to enumTest 
    enumTest(ONE | TWO | FIVE); 
    enumTest(TWO | THREE | FOUR | FIVE); 
    enumTest(ONE | TWO | THREE | FOUR | FIVE | SIX); 

    return 0; 
} 

以下過載這是否超載真正提供類型安全?包含未在枚舉中定義的值的int是否會導致未定義的行爲?是否有任何警告要注意?

+0

'運營商|(五,六)== 0x30'。什麼ENUM常量的值爲0x30? – Adam

+0

我不會對結果值做任何形式的比較,我只是在檢查標誌。 – ctor

+1

然後OR的結果應該保持爲int。 – Adam

回答

5

這是否超載真正提供類型安全?

在這種情況下,是的。枚舉值的有效範圍至少達到(但不一定包括)最大的指定枚舉數之後的二的最大次冪,以便允許它用於像這樣的位掩碼。因此,對兩個值的任何按位操作都會給出可由此類型表示的值。

是否投射包含未在enum中定義的值的int會導致未定義的行爲?

不,只要這些值可由枚舉表示,它們就在這裏。

是否有任何需要注意的注意事項?

如果您正在進行算術等操作,可能會超出範圍,那麼您會得到一個實現定義的結果,但不是未定義的結果。

+0

「只要值可以通過枚舉來表示」,你的意思是這些值必須可以由枚舉的基礎類型來表示? – Carlton

3

常量的值在OR下沒有關閉。換句話說,它是可能兩個ENUM常量的OR的結果將導致這不是一個枚舉常量的值:

0x30 == FIVE | SIX; 

標準說,這是確定的,一個enumaration可以有一個值不等於它的任何啓動器(常量)。據推測,這是允許這種類型的用法。

在我看來這不是類型安全的,因爲如果你看的enumTest實施你必須意識到參數類型爲ENUM,但它可能有一個值,這不是一個ENUM枚舉。

我認爲,如果這些只是位標誌,那麼做編譯器希望你:使用int標誌的組合。

+1

只要結果是一個有效的枚舉值,演員就可以很好地定義 - 就像它在這裏一樣。有效值不僅僅是指定的枚舉數,而是(至少)所有的一切都達到了下一個最大的兩個數 - 它就是這樣指定的,以便準確地使用這種用法。 –

+0

@MikeSeymour你有參考嗎?我發現的一切都是非法的。 – Adam

+0

C++ 11 7.2。第7段是最相關的,因爲它定義了值的範圍。 –

2

用一個簡單的enum如你:

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    ... 
}; 

是實現定義什麼是基本類型(很可能int),但只要你會使用|(按位或)創建掩碼時,結果永遠不會需要比此枚舉的最大值更寬的類型。


[1]「的基本類型枚舉的是,可以表示所有在枚舉所定義的枚舉值的整數類型,它是實現定義其積分類型用於作爲除非基本類型不得大於int,除非枚舉器的值不適合intunsigned int。「

4

如果你仔細想想類型安全,最好是使用std::bitset

enum BITS { A, B, C, D }; 
std::bitset<4> bset, bset1; 
bset.set(A); bset.set(C); 
bset1[B] = 1; 
assert(bset[A] == bset[C]); 
assert(bset[A] != bset[B]); 
assert(bset1 != bset); 
1

這是我的方法的位標誌:

template<typename E> 
class Options { 
     unsigned long values; 
     constexpr Options(unsigned long v, int) : values{v} {} 
    public: 
     constexpr Options() : values(0) {} 
     constexpr Options(unsigned n) : values{1UL << n} {} 
     constexpr bool operator==(Options const& other) const { 
     return (values & other.values) == other.values; 
     } 
     constexpr bool operator!=(Options const& other) const { 
     return !operator==(other); 
     } 
     constexpr Options operator+(Options const& other) const { 
     return {values | other.values, 0}; 
     } 
     Options& operator+=(Options const& other) { 
     values |= other.values; 
     return *this; 
     } 
     Options& operator-=(Options const& other) { 
     values &= ~other.values; 
     return *this; 
     } 
}; 

#define DECLARE_OPTIONS(name) class name##__Tag; using name = Options 
#define DEFINE_OPTION(name, option, index) constexpr name option(index) 

您可以使用它像這樣:

DECLARE_OPTIONS(ENUM); 
DEFINE_OPTIONS(ENUM, ONE, 0); 
DEFINE_OPTIONS(ENUM, TWO, 1); 
DEFINE_OPTIONS(ENUM, THREE, 2); 
DEFINE_OPTIONS(ENUM, FOUR, 3); 

然後ONE + TWO仍然ENUM類型。您可以重新使用該類來定義多個不同的不兼容類型的位標記集。

我個人不喜歡用|&來設置和測試比特。這是設置和測試需要完成的邏輯操作,但除非您考慮按位操作,否則它們不會表達操作的含義。如果你讀出ONE | TWO,你可能會認爲你想要一個或兩個,而不一定是兩個。這就是爲什麼我更喜歡使用+來添加標誌和==來測試是否設置標誌。

看到這裏我的建議的執行情況的詳細信息:http://www.crisluengo.net/index.php/archives/851