切换枚举的过程是什么?每个枚举都包含在案例中?理想情况下,您希望代码能够成为未来的证明,您是如何做到的?
另外,如果一些白痴向枚举类型强制转换了什么呢?是否应该考虑这种可能性?或者我们是否应该假设在代码审查中会发现这样一个令人震惊的错误?
enum Enum
{
Enum_One,
Enum_Two
};
Special make_special( Enum e )
{
switch( e )
{
case Enum_One:
return Special( /*stuff one*/ );
case Enum_Two:
return Special( /*stuff two*/ );
}
}
void do_enum( Enum e )
{
switch( e )
{
case Enum_One:
do_one();
break;
case Enum_Two:
do_two();
break;
}
}
assert(false)
; 我特别感兴趣的是你为什么选择这样做。
答案 0 :(得分:30)
我抛出异常。正如鸡蛋是鸡蛋一样,有人会将一个带有错误值而不是枚举值的整数传递给你的交换机,最好是吵闹失败,但是给程序提供错误的可能性,断言()不会。 / p>
答案 1 :(得分:23)
我会放assert
。
Special make_special( Enum e )
{
switch( e )
{
case Enum_One:
return Special( /*stuff one*/ );
case Enum_Two:
return Special( /*stuff two*/ );
default:
assert(0 && "Unhandled special enum constant!");
}
}
不处理枚举值,而意图是覆盖所有情况,代码中的错误是需要修复的。错误无法从“优雅”解决或处理,应立即修复(所以我不会抛出)。为了使编译器对“返回无值”警告保持安静,请调用abort
,如此
#ifndef NDEBUG
#define unreachable(MSG) \
(assert(0 && MSG), abort())
#else
#define unreachable(MSG) \
(std::fprintf(stderr, "UNREACHABLE executed at %s:%d\n", \
__FILE__, __LINE__), abort())
#endif
Special make_special( Enum e )
{
switch( e )
{
case Enum_One:
return Special( /*stuff one*/ );
case Enum_Two:
return Special( /*stuff two*/ );
default:
unreachable("Unhandled special enum constant!");
}
}
编译器没有关于没有值的返回的警告,因为它知道abort
永远不会返回。在我看来,我们立即终止失败的程序,这是唯一合理的反应(尝试继续运行导致未定义行为的程序是没有意义的。)
答案 2 :(得分:7)
首先,我会总是在default
语句中有switch
。即使没有白痴将int
egers投射到enum
,也总是存在default
可以帮助捕获的内存损坏的可能性。对于它的价值,MISRA规则使默认值成为必需。
关于你做什么,这取决于具体情况。如果可以很好地处理异常,请处理它。如果它是代码的非关键部分中的状态变量,请考虑将状态变量静默地重置为初始状态并继续(可能记录错误以供将来参考)。如果它会导致整个程序以一种非常混乱的方式崩溃,那么试着优雅或者某种方式。简而言之,这一切都取决于你switch
的内容以及不正确的值有多糟糕。
答案 3 :(得分:6)
作为补充说明(除了其他回复之外)我还要注意,即使在C ++语言中,它具有相对严格的类型安全限制(至少与C相比),也可以生成枚举值通常情况下,类型可能与任何枚举器都不匹配,而不使用任何“黑客”。
如果你有一个枚举类型E
,你可以合法地执行此操作
E e = E();
将使用零值初始化e
。即使E
的声明不包含代表0
的枚举常量,这在C ++中也是完全合法的。
换句话说,对于任何枚举类型E
,表达式E()
格式正确并且生成类型为E
的零值,无论E
如何定义
请注意,这个漏洞允许我们在不使用任何“黑客”的情况下创建潜在的“意外”枚举值,例如您在问题中提到的枚举类型的int
值的转换。
答案 4 :(得分:2)
你的物品很好。但我会删除'抛出可捕捉异常'。
附加:
答案 5 :(得分:1)
我倾向于做选项2:
添加一个抛出可捕获异常的默认案例
这应该突出问题,如果它发生了,它只需要花费几行来实现。
答案 6 :(得分:1)
作为进一步的选择:避免切换枚举。
答案 7 :(得分:1)
断言然后可能会抛出。
对于与此相同的项目中的内部代码(您没有说明函数边界是什么 - 内部库,外部库,内部模块,......)它将在开发期间断言。这就是你想要的。
如果代码是供公众消费(其他团队,出售等),那么断言将消失,你就会抛弃。这对外部消费者来说更有礼貌
如果代码总是内部的,那么只需断言
答案 8 :(得分:1)
我的0.02美元:
如果此方法在外部可见(由您无法控制的代码调用),那么您可能需要处理某人向您发送无效枚举值的可能性。在这种情况下,抛出异常。
如果此方法是您的代码的内部(只有您调用它),那么断言应该是所有必要的。这将记录有一天添加新枚举值并忘记更新switch语句的情况。
始终在每个开关中提供默认情况,并且至少在断言时断言。这种习惯可以通过每隔一段时间节省数小时甚至数天的头部刮伤来获得回报。
答案 9 :(得分:1)
除了建议抛出异常之外,如果你使用 gcc ,你可以使用 -Wswitch-enum (并最终 -Werror = switch -enum )如果您的枚举成员在任何情况下都没有出现,它将添加警告(或错误)。 可能有其他编译器的等价物,但我只在gcc上使用它。
答案 10 :(得分:1)
LLVM编码标准'意见是:Don’t use default labels in fully covered switches over enumerations
他们的理由是:
如果在枚举上没有默认标签的开关未覆盖每个枚举值,则
-Wswitch
会发出警告。如果在枚举上的完全覆盖的开关上写入默认标签,则在将新元素添加到该枚举时不会触发-Wswitch
警告。为了避免添加这些类型的默认值,Clang默认警告-Wcovered-switch-default
,但在构建支持警告的Clang版本的LLVM时打开警告。
基于此,我个人喜欢这样做:
enum MyEnum { A, B, C };
int func( MyEnum val )
{
boost::optional<int> result; // Or `std::optional` from Library Fundamental TS
switch( val )
{
case A: result = 10; break;
case B: result = 20; break;
case C: result = 30; break;
case D: result = 40; break;
}
assert( result ); // May be a `throw` or any other error handling of choice
... // Use `result` as seen fit
return result;
}
如果您在交换机的每个案例中选择return
,则不需要boost::optional
:只需在std::abort()
之后无条件地throw
或switch
{1}}阻止。
重要的是要记住,switch
可能不是最好的设计工具(如@UncleBens answer中所述):多态或某种类型的查找表可以提供更好的解决方案,特别是如果你的枚举有很多元素。
PS:好奇心,Google C++ Style Guide对switch
s-over - enum
没有default
个案例提出异议:
如果不以枚举值为条件,则switch语句应始终具有默认大小写
答案 11 :(得分:0)
在类似于提供的示例的情况下,您实际上可以将选项1与其他选项之一组合: 省略默认值并启用相应的编译器警告(如果可用)。这样,如果添加了新的枚举值,您可以立即找到并且可以方便地添加大小写,而无需保证在运行时执行特定的代码路径以便找到它。
然后在开关结束和函数结束之间添加代码来断言或抛出(我更喜欢断言,因为这确实是无效的情况)。这样,如果有人将int转换为你的枚举类型,你仍然可以进行运行时检查。
答案 12 :(得分:0)
我不是C ++人。但是,我会写它像
enum Enum
{
Enum_One,
Enum_Two
};
Special make_special( Enum e )
{
if(Enums.IsDefined(typeof(Enum),(int)e))
{
switch( e )
{
case Enum_One:
return Special( /*stuff one*/ );
case Enum_Two:
return Special( /*stuff two*/ );
}
}
// your own exception can come here.
throw new ArgumentOutOfRangeException("Emum Value out of range");
}
答案 13 :(得分:0)
除了第一个,我个人推荐您的任何解决方案。保留默认情况,并断言,抛出(您喜欢的任何异常类型)。在C#中,如果省略默认情况,Visual Studio不会发出警告。
我建议你添加案例并且失败的原因是这是代码维护的重要一点。只要有人添加到枚举列表中,switch语句就必须增长到匹配。
另外(正如UncleBen所说),看看你是否可以通过使用多态来设计整个场景。这样,当您向枚举添加新值时,您只需将其添加到一个位置。每次看到enum上的开关时,都应该考虑使用多态。
答案 14 :(得分:0)
除了在运行时首选解决方案的异常(并且将处理疯狂的转换)之外,我倾向于使用静态编译时断言。
您可以执行以下操作:
//this fails at compile time when the parameter to the template is false at compile time, Boost should provide a similar facility
template <COLboolean IsFalse> struct STATIC_ASSERTION_FAILURE;
template <> struct STATIC_ASSERTION_FAILURE<true>{};
#define STATIC_ASSERT( CONDITION_ ) sizeof( STATIC_ASSERTION_FAILURE< (COLboolean)(CONDITION_) > );
//
// this define will break at compile time at locations where a switch is being
// made for the enum. This helps when adding enums
//
// the max number here should be updated when a new enum is added.
//
// When a new enum is added, at the point of the switch (where this
// define is being used), update the switch statement, then update
// the value being passed into the define.
//
#define ENUM_SWITCH_ASSERT( _MAX_VALUE )\
STATIC_ASSERT( _MAX_VALUE == Enum_Two)
enum Enum
{
Enum_One = 0,
Enum_Two = 1
};
然后在你的代码中,每当你使用枚举集:
ENUM_SWITCH_ASSERT( Enum_Two )
switch( e )
{
case Enum_One:
do_one();
break;
case Enum_Two:
do_two();
break;
}
现在每当您更改宏ENUM_SWITCH_ASSERT以处理新的枚举值时,它将在编译时在使用枚举集的位置附近中断。在添加新案例时帮助很多。
答案 15 :(得分:0)
因为它可能有助于知道枚举的意外值,所以写下你自己的BADENUM(枚举)宏,如下所示:
#define _STRIZE(x) _VAL(x)
#define _VAL(x) #x
extern void __attribute__ ((noreturn)) AssertFail(const char *Message);
#define ASSERT(Test) ((Test) ? (void)0 : AssertFail(__FILE__ ":" _STRIZE(__LINE__) " " #Test))
extern void __attribute__ ((noreturn)) BadEnum(const char *Message, const long unsigned Enum);
#define BADENUM(Enum) BadEnum(__FILE__ ":" _STRIZE(__LINE__), (u32)Enum))