2011-02-11 87 views
36

我正在讀取來自二進制文件的enum值,並希望檢查該值是否真的是enum值的一部分。我該怎麼做?如何檢查枚舉值是否有效?

#include <iostream> 

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    switch (v2) 
    { 
     case A: 
      std::cout<<"A"<<std::endl; 
      break; 
     case B: 
      std::cout<<"B"<<std::endl; 
      break; 
     case C: 
      std::cout<<"C"<<std::endl; 
      break; 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
} 

是否必須使用switch運算符還是有更好的方法嗎?

編輯

我有枚舉值設定,不幸的是我無法修改它們。更糟糕的是,他們是不連續的(它們的值變爲0,75,76,80,85,90,95,100等)

+3

任何枚舉只是一個數字,所以我不認爲有更好的方法來檢查它。您可能應該爲您的數據類型定義更嚴格的結構。 – Rizo

回答

21

enum如果C++的值在下面的標準規則定義的範圍[A,B]內,則該值在C++中有效。所以在enum X { A = 1, B = 3 }的情況下,2的值被認爲是一個有效的枚舉值。

考慮標準的7.2/6:

對於枚舉其中額敏是最小的枚舉和E max是最大的,枚舉的值在範圍BMIN基礎類型的到BMAX的值,其中bmin和bmax分別是可存儲emin和emax的最小位域的最小值和最大值。可以定義一個枚舉值,它的值不是由任何枚舉器定義的。

在C++中沒有回顧。一種方法是額外列出數組中的枚舉值,並編寫一個可以進行轉換的封裝器,並可能在失敗時引發異常。

請參閱Similar Question關於如何將int轉換爲枚舉以獲取更多詳細信息。

+1

你誤解了標準報價,有效值超過'[A,B]'。 –

+5

的確,例如如果值是1和5,那麼後者至少需要3位,所以6和7也將是枚舉器的有效值。 – visitor

+0

@Matthieu:謝謝,編輯了答案。 – Leonid

9

也許用枚舉這樣的:

enum MyEnum 
{ 
A, 
B, 
C 
}; 

,並檢查

if (v2 >= A && v2 <= C) 

如果您不指定枚舉常量的值,那麼值將從零開始,並隨每次向下移動列表而增加1。例如,給定 enum MyEnumType { ALPHA, BETA, GAMMA }; ALPHA具有爲0的值,BETA具有1的值,並且GAMMA具有2

+1

我喜歡這種簡單性,並通過將枚舉中的第一個項目定義爲SOMETYPE_UNKNOWN,並將最後一個項目定義爲SOMETYPE_MAX來擴展它。然後,測試將始終爲AssertTrue(v2> = SOMETYPE_UNKNOWN && v2 <= SOMETYPE_MAX)。當然,只有在UNKNOWN和MAX之前添加項目。 –

+0

我一開始並不理解你的想法,但它確實是一個很好的零維護技巧! – pfabri

2

談到一種語言的值,也沒有更好的辦法,枚舉值存在編譯時間只有編程方式無法枚舉它們。不過,有了一個精心設計的基礎架構,您仍然可以避免多次列出所有值。見Easy way to use variables of enum types as string in C?

你的樣品可以再使用「enumFactory.h」只要是被改寫:

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    #define CHECK_ENUM_CASE(name,assign) case name: std::cout<< #name <<std::endl; break; 
    switch (v2) 
    { 
     ABC_ENUM(CHECK_ENUM_CASE) 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
    #undef CHECK_ENUM_CASE 
} 

甚至(使用一些設施在這頭已經存在):

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 
DEFINE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 
    const char *name = GetString(v2); 
    if (name[0]==0) name = "no match found"; 
    std::cout << name << std::endl; 
} 
7

我發現讓它變得「簡單」的唯一方法是創建(宏)枚舉的排序數組並檢查它。

switch技巧因enum失敗,因爲enum可能有多個具有給定值的枚舉器。

這真是一個煩人的問題。

3

爲C++託管擴展支持的語法如下:

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

Enum::IsDefined(Abc::typeid, 8); 

參考:MSDN「Managed Extensions for C++ Programming

+0

我不確定什麼是「託管C++」,但你確定它是C++,而不是c#嗎? [本](http://msdn.microsoft.com/en-us/library/system.enum.isdefined%28v=vs.110%29。aspx)看起來像c# –

+3

@BЈовић:'managed C++'是'C++'的Microsoft變體,它能夠使用'.NET框架'的庫。它看起來像'C++',因爲'::'運算符沒有像'c#'那樣定義。 – Stefan

+0

@BЈовић您是否嘗試過託管擴展C++項目中的代碼?我們在我們的一個C++項目中使用了類似的代碼。具體而言,我們使用Enum :: IsDefined()方法。 – Brett

4

在C++ 11有一個更好的方式,如果你準備列出你的枚舉值作爲模板參數。您可以將此視爲一件好事,讓您可以在不同的上下文中接受有效枚舉值的子集;在解析來自外部的代碼時通常很有用。

下面的例子可能有用的補充是圍繞EnumType相對於IntType的基礎類型的一些靜態斷言,以避免截斷問題。作爲練習留下。

#include <stdio.h> 

template<typename EnumType, EnumType... Values> class EnumCheck; 

template<typename EnumType> class EnumCheck<EnumType> 
{ 
public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType) { return false; } 
}; 

template<typename EnumType, EnumType V, EnumType... Next> 
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...> 
{ 
    using super = EnumCheck<EnumType, Next...>; 

public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType v) 
    { 
     return v == static_cast<IntType>(V) || super::is_value(v); 
    } 
}; 

enum class Test { 
    A = 1, 
    C = 3, 
    E = 5 
}; 

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>; 

void check_value(int v) 
{ 
    if (TestCheck::is_value(v)) 
     printf("%d is OK\n", v); 
    else 
     printf("%d is not OK\n", v); 
} 

int main() 
{ 
    for (int i = 0; i < 10; ++i) 
     check_value(i); 
} 
+0

由於'int v'的值在編譯時並不知道,所以'is_value'必須在運行時執行。這不會導致所有類型的遞歸函數調用,並且與簡單的switch語句或所有值的數組相比效率非常低?你仍然需要列出所有的枚舉值,所以這不像你用這種方法獲得任何東西。或者我在這裏錯過了什麼? –

+0

@InnocentBystander它們都是'constexpr'函數,所以編譯器有很多優化的空間。函數也不是遞歸的;它是一個恰好具有相同名稱的功能鏈。在上面例子的一些快速測試中,gcc 5.4爲模板版本和交換機版本生成一個短一個指令的代碼。對於模板版本,Clang 3.8是兩個更長的說明。結果取決於有多少個值以及值是否連續。最大的收穫,特別是在進行協議解碼時,你是在一行中寫出你期望的代碼。 – janm

+1

你是對的 - 抱歉不是「遞歸」本身,而是函數調用鏈。有趣的是編譯器可以優化所有這些。並感謝您在3年前回答:) –

2

還挺死靈,但是......讓INT到第一/最後一個枚舉值的範圍檢查(可以用janm的想法相結合,進行精確的檢查),C++ 11:

頁眉:

namespace chkenum 
{ 
    template <class T, T begin, T end> 
    struct RangeCheck 
    { 
    private: 
     typedef typename std::underlying_type<T>::type val_t; 
    public: 
     static 
     typename std::enable_if<std::is_enum<T>::value, bool>::type 
     inrange(val_t value) 
     { 
      return value >= static_cast<val_t>(begin) && value <= static_cast<val_t>(end); 
     } 
    }; 

    template<class T> 
    struct EnumCheck; 
} 

#define DECLARE_ENUM_CHECK(T,B,E) namespace chkenum {template<> struct EnumCheck<T> : public RangeCheck<T, B, E> {};} 

template<class T> 
inline 
typename std::enable_if<std::is_enum<T>::value, bool>::type 
testEnumRange(int val) 
{ 
    return chkenum::EnumCheck<T>::inrange(val); 
} 

枚舉聲明:

enum MinMaxType 
{ 
    Max = 0x800, Min, Equal 
}; 
DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal); 

用法:

bool r = testEnumRange<MinMaxType>(i); 

上面提到的主要區別是測試函數只依賴枚舉類型本身。

0

另一種方式來做到這一點:

#include <algorithm> 
#include <iterator> 
#include <iostream> 

template<typename> 
struct enum_traits { static constexpr void* values = nullptr; }; 

namespace detail 
{ 

template<typename T> 
constexpr bool is_value_of(int, void*) { return false; } 

template<typename T, typename U> 
constexpr bool is_value_of(int v, U) 
{ 
    using std::begin; using std::end; 

    return std::find_if(begin(enum_traits<T>::values), end(enum_traits<T>::values), 
     [=](auto value){ return value == static_cast<T>(v); } 
    ) != end(enum_traits<T>::values); 
} 

} 

template<typename T> 
constexpr bool is_value_of(int v) 
{ return detail::is_value_of<T>(v, decltype(enum_traits<T>::values) { }); } 

//////////////////// 
enum Abc { A = 4, B = 8, C = 12 }; 

template<> 
struct enum_traits<Abc> { static constexpr auto values = { A, B, C }; }; 
decltype(enum_traits<Abc>::values) enum_traits<Abc>::values; 

enum class Def { D = 1, E = 3, F = 5 }; 

int main() 
{ 
    std::cout << "Abc:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Abc>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    std::cout << "Def:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Def>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    return 0; 
} 

這種做法恕我直言的「醜陋」的部分是有定義:

decltype(enum_traits<Abc>::values) enum_traits<Abc>::values 

如果你不反對的宏,你可以用它裏面的宏:

#define REGISTER_ENUM_VALUES(name, ...) \ 
template<> struct enum_traits<name> { static constexpr auto values = { __VA_ARGS__ }; }; \ 
decltype(enum_traits<name>::values) enum_traits<name>::values;