11

假设我有一个枚举。

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE};

后来我有这样的功能:

void Function (ShapeName const shape){

    switch (shape){
        case ShapeName::TRIANGLE:
            DoSomething1();
            break;

        case ShapeName::CIRCLE:
            DoSomething2();
            break;

        case ShapeName::SQUARE: 
            DoSomething3();
            break;

        default:
            //THIS CODE BLOCK SHOULD NEVER BE EXECUTED!
    }

    return;
}

尽管不应该执行默认标签,但我想说明如果程序员将另一个值添加到“ShapeName”而可能出现的潜在错误,并且在切换中没有考虑到它。
你会建议我做什么?

1. 断言
我可以使用断言,但我在断言什么?

assert(false); //?

2. 异常
我可以抛出异常,但我认为这不是很好的做法。我的印象是异常是由于某些环境而无法预测的运行时事件。

3. 退出
我可以立即退出程序并出现错误。这感觉是最好的主意,但我不确定这是否是好的做法。我认为断言的优点是你可以在准备好发布程序时将它们全部关闭。然后,所有的断言代码都将不复存在。


也许还有另一种我不知道的方式。我确实使用了一个编译器标志来警告未解释的值,但我仍然想知道其他人推荐什么。

4

4 回答 4

9

我喜欢用信息丰富的信息来断言的想法。尝试这个:

assert (!"The default case of so-so switch was reached.");

这始终返回 false,但提供了您可以使用的消息。

编辑:
我找到了我从记忆中提取这个概念的来源;它在以下书中:
C++ 编码标准 - 101 规则和指南

于 2012-04-28T02:24:10.197 回答
4

我对这种特定情况的偏好,即您打开枚举标记并处理所有情况,是省略默认值。这样,对于任何合理的编译器,如果有人将新标签添加到枚举而不是交换机,您将收到有关未在交换机中处理的标签的编译时警告。

于 2012-04-28T02:32:40.670 回答
0

Short answer: Use polymorphism to eliminate the problem.

Long answer: The easiest way to solve the root problem (i.e. How to prevent switch statements with missing entries?) would be to avoid using switch statements if there's a chance of entries being forgotten. Switch statements are useful in local contexts, but if the logic they represent is duplicated in multiple locations then there's a possibility of forgetting to update one and you're setting yourself up for runtime errors.

class Shape
{
    // functionality MUST be added for new shape types
    // or a compile error will occur
    virtual void DoSomething() const = 0;
};

void Function (Shape const & shape){
    return shape.DoSomething();
}

The switch statement may still be useful at the site of creation:

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE};

unique_ptr< Shape > MakeShape (ShapeName const shape){
    switch (shape){
        case ShapeName::TRIANGLE:
            return unique_ptr< Shape >( new Triangle() );

        case ShapeName::CIRCLE:
            return unique_ptr< Shape >( new Circle() );

        case ShapeName::SQUARE: 
            return unique_ptr< Shape >( new Square() );
     }

     throw std::runtime_error();
     // or whichever option you prefer from the other answers
}

But that's the only place the logic exists. And even that can be improved upon with static polymorphism:

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE,ELLIPSE};

template< ShapeName shape >
unique_ptr< Shape > MakeShape();

template<>
unique_ptr< Shape > MakeShape< ShapeName::TRIANGLE >()
{
    return unique_ptr< Shape >( new Triangle() );
}

template<>
unique_ptr< Shape > MakeShape< ShapeName::CIRCLE >()
{
    return unique_ptr< Shape >( new Circle() );
}

template<>
unique_ptr< Shape > MakeShape< ShapeName::SQUARE >()
{
    return unique_ptr< Shape >( new Square() );
}

int main()
{
    MakeShape< ShapeName::TRIANGLE >(); // okay

    MakeShape< ShapeName::ELLIPSE >(); // compile error
}

See Martin Folwer for more info.

于 2012-04-28T11:30:58.710 回答
0

我想对我来说这在某种程度上取决于 DoSomething# 方法的作用。

如果稍后它会导致不那么优雅的崩溃,那么您肯定希望在调用它时中断运行时,并解释“使用无效枚举调用的函数:enumName”或向开发人员通知他们没有更新此 switch 语句的东西,而不是让他们想知道以后是否会失败。

于 2012-04-28T02:27:28.417 回答