2011-01-25 82 views
12

比方说,我有这样的事情:检测是否铸造一个int到枚举结果到非枚举值

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES}; 

CardColor MyColor = static_cast<CardColor>(100); 

是否有一个(简单)的方法来检测,无论是在编译时或在运行时,MyColor的值不符合任何枚举值?

并且更一般地,如果枚举值没有关注彼此,例如:

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES}; 
+2

在运行时还是编译时? – CashCow 2011-01-25 16:24:25

+3

除“不这样做”? – 2011-01-25 16:24:56

+0

@CashCow:恩,都!我更新了我的问题。 – 2011-01-25 16:26:42

回答

15

CashCow将a decent answer提交给这个问题:编写一个自定义函数来执行一个checked cast是很简单的。

不幸的是,这也是很多工作,您必须确保它与枚举同步,以便枚举定义中的枚举器列表与checked函数中的枚举器列表相同。您还必须为每个您希望能够执行checked cast的枚举编写其中的一个。

不用做所有这些手动工作,我们可以使用预处理器(通过Boost预处理器库的一些帮助)自动生成所有这些代码。这里是一个宏,它产生一个枚举定义以及一个checked_enum_cast函数。看起来可能有些可怕(代码生成宏往往是可怕的),但这是一个非常有用的技术,可以熟悉。

#include <stdexcept> 
#include <boost/preprocessor.hpp> 

// Internal helper to provide partial specialization for checked_enum_cast 
template <typename Target, typename Source> 
struct checked_enum_cast_impl; 

// Exception thrown by checked_enum_cast on cast failure 
struct invalid_enum_cast : std::out_of_range 
{ 
    invalid_enum_cast(const char* s) 
     : std::out_of_range(s) { } 
}; 

// Checked cast function 
template <typename Target, typename Source> 
Target checked_enum_cast(Source s) 
{ 
    return checked_enum_cast_impl<Target, Source>::do_cast(s); 
} 

// Internal helper to help declare case labels in the checked cast function 
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem: 

// Macro to define an enum with a checked cast function. name is the name of 
// the enumeration to be defined and enumerators is the preprocessing sequence 
// of enumerators to be defined. See the usage example below. 
#define DEFINE_SAFE_CAST_ENUM(name, enumerators)       \ 
    enum name                \ 
    {                  \ 
     BOOST_PP_SEQ_ENUM(enumerators)          \ 
    };                  \ 
                      \ 
    template <typename Source>            \ 
    struct checked_enum_cast_impl<name, Source>       \ 
    {                  \ 
     static name do_cast(Source s)          \ 
     {                 \ 
      switch (s)              \ 
      {                \ 
      BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \ 
       return static_cast<name>(s);        \ 
      default:              \ 
       throw invalid_enum_cast(BOOST_PP_STRINGIZE(name));   \ 
      }                \ 
      return name();             \ 
     }                 \ 
    }; 

这里是你将如何使用您的CardColor例如:

DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS)) 

int main() 
{ 
    checked_enum_cast<CardColor>(1); // ok 
    checked_enum_cast<CardColor>(400); // o noez! an exception! 
} 

第一行替换您enum CardColor ...定义;它定义枚举并提供专门化,使您可以使用checked_enum_cast将整数转换为CardColor

这可能看起来像很多麻烦只是为您的枚举获得一个checked cast函数,但这种技术是非常有用和可扩展的。你可以添加各种各样的功能。例如,我有一个生成函数来将枚举类型转换为字符串表示和函数,这些字符串表示和函数执行其他几次转换并检查我用于大多数枚举的函数。请记住,你必须编写和调试那个大的,丑陋的宏只是一次,然后你可以在任何地方使用它。

3

这是常见的枚举,它指示在它的项数的端部有一个附加的元件。您可以使用该值在运行时检查该值是否有效:

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES, CARDS_COUNT}; 

CardColor MyColor = static_cast<CardColor>(100); 

if (MyColor >= CARDS_COUNT) { 
    /* Invalid value */ 
} 
+3

不,不是。你真的想要一个可接受的CardColor为CARDS_COUNT吗? – wheaties 2011-01-25 16:28:33

1

开始时 - 这样做不好主意。

但是如果你想的话,建议硬编码枚举的整数值:

enum CardColor { HEARTS = 10, DIAMONDS = 11, CLUBS = 12, SPADES = 13}; 

然后重载分配新建分配FY操作:

CardColor operator = (int value) 
{ 
    switch (value) 
    { 
     case 10: 
      return HEARTS; 
     // case for other values 
     default: 
      // throw an exception or something 
    } 
} 
8

最简单的运行时的解决办法是不使用的static_cast,但使用一个为你做检查的函数。如果你把你的枚举放在一个类中,你可以通过这个类来完成。 类似于:

class CardCheck 
{ 
public: 
    enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES }; 

    explicit CardCheck(int x) : c(static_cast<CardColor>(x)) 
    { 
    switch(c) 
    { 
     case HEARTS: case DIAMONDS: case CLUBS: case SPADES: 
      break; 

     default: 
     // assert or throw 
    } 
    } 

    CardColor get() const 
    { 
    return c; 
    } 

private: 
    CardColor c;  
}; 
0

枚举值可能重叠或有空洞;同样,实际变量可以赋值为零,集合中的任何值或允许值的位或值。所以:

enum suites { hearts, diamonds, clubs, spades }; 

允许值0,1,2,3;

enum suites { hearts = 1 << 0, diamonds = 1 << 1, clubs = 1 << 2, spades = 1 << 4 }; 

允许从0到15

任何值如果使用一个枚举来定义的位值,它通常是一个好主意,以限定(二进制)operator&operator|operator&=operator|=。如果你不这样做,只要一个不在集合中的值被生成,你将需要一个明确的转换,所以发生这种情况的地方很容易被发现。

有编译器可以警告,如果超出允许范围的数字被分配,或者如果不是,则第一个或所有名称都附加了初始值设定项(这是违反MISRA-C规则)。

4

支持动态溢出检查。见-fsanitize=enum开关。使用此开关编译的程序将通过标准错误输出发出枚举赋值错误。这将允许您执行调试测试。在官方构建中测试可疑输入不合适。