在开关情况下避免默认值的优雅方式(使用枚举类)



我有一个枚举类,例如:

enum class State{
S1,
S2,
S3,
S4
};

每当我做出可能使用此类的switch/case语句时,我都希望避免使用";默认";不惜一切代价,强迫我为所有可能的案件写一份声明。这个想法是,如果我在这个枚举中添加一个新的事例;S5〃;,由于缺少默认值,编译器会在每个开关处向我发送警告,因为并非所有情况都涵盖在内。通过这样做,我最终不会忘记这个新状态可能有特定行为需要实现的地方。

问题是,有各种各样的开关/情况,其中只有一些枚举情况的实现:

switch(state)
{
case S1: 
doSomething1();
break;
case S2: 
doSomething2();
break;
case S3: 
break;
case S4:
break;
}

但我不太喜欢这些五花八门的";"空";没有行为的州的案例,然后中断。这正是";默认";会更优雅,但正如我刚才解释的,这是我想避免的。

有没有一种更优雅(或更高效?(的方式来实现我在这里想要做的事情?因为我知道其他编程语言为switch/case语句提供了更高级的语法,但我不确定C++(更具体地说是C++17(的可能性。

对于切换情况,我更喜欢做以下操作:

enum class my_enum {
E1,
E2,
E3,
E4,
E5
};
result_type do_action_based_on_enum(my_enum e)
{
switch(e) {
case my_enum::E1:
return action_e1();
case my_enum::E2:
return action_e2();
case my_enum::E3:
case my_enum::E4:
case my_enum::E5:
return action_default();
}
}

在每种情况下返回以避免每次写入break,并在适当的时候通过测试用例。

"优雅"在旁观者的眼中;但我发现,使用宏来生成处理枚举的代码序列大大减少了冗余和出错的机会。这是少数几个使用预处理器的合理情况之一(除了条件编译和include之外(。在大型和分布式项目中,这通常会带来更多的红利。

其想法是从相同的文本生成枚举本身以及切换用例和容器,如映射或数组必须在一个单独的文件中,以便可以多次包含相同的副本。这使得在没有相关操作的情况下拥有枚举成员在物理上是不可能的。添加另一个状态就像在enum-actions.h中添加一行一样微不足道;所有的使用,如映射条目、切换用例或从该列表生成的else-if链,都会自动调整。由于include依赖关系,这些地方仍然需要重新编译,但不需要对它们进行修改。

这是一个包含枚举成员名称、值和相关操作的文件(在生成开关/事例时,这些操作必须是函数名或函子(。我给它加了一个.h后缀,因为它会被包含在内,尽管它不是语法C++;也可以给它加一个.txt后缀。

枚举操作.h

// A list of enum identifiers with associated values 
// and actions usable in macro definitions.
// This macro is used to construct both the actual enum
// as well as the cases in the action switch. This way
// it is impossible to have enum members without an associated action.
// There is no "real" enum definition elsewhere; this is it.
ENUM_ACTION(S1, 2, action1)
ENUM_ACTION(S2, 23, action2)
ENUM_ACTION(S3, 997, no_action)

状态E.h

// Define the states enum from the macro list of enum/action pairs.
// The last member will have a trailing comma as well, which 
// is permitted by the C++ grammar exactly for this use case of code generation.
enum stateE
{
#   define ENUM_ACTION(state, value, action) state = value,
#   include "enum-actions.h"
#   undef ENUM_ACTION
};

关联枚举.cpp

#include <iostream>
#include "stateE.h"
// Dummy actions for states
void action1() { std::cout << __func__ << "n"; }
void action2() { std::cout << __func__ << "n"; }
// pseudo action when nothing should happen
void no_action() { std::cout << __func__ << "n"; }

/// Perform the action associated with the state. This is done with a 
/// switch whose cases are constructed from the list
/// in enum-actions.h.
void actOnState(stateE stateArg)
{
switch (stateArg)
{
#       define ENUM_ACTION(state, value, action) case state: action(); break;
#       include "enum-actions.h"
#       undef ENUM_ACTION
}
}
int main()
{
actOnState(S1);
actOnState(S2);
actOnState(S3);
}

示例会话:

$ g++ -Wall -o associative-enum associative-enum.cpp && ./associative-enum
action1
action2
no_action

我们还可以使用关联容器来存储处理程序(lambdas或functors(,然后在找到键后调用处理程序。

添加use断言以确保所有枚举都有相应的处理程序(但这不是编译时错误,我们可以在这里存储函数指针来进行编译时检查,但这会使代码变得丑陋(。

#include <cassert>
#include <functional>
#include <iostream>
#include <map>
enum class State { S1, S2, S3, S4, Count };
int main(int argc, char* argv[]) {
auto dummy_handler = []() {};
std::map<State, std::function<void()>> mapping = {
{State::S1, []() { std::cout << "s1n"; }},
{State::S2, []() { std::cout << "s2n"; }},
{State::S3, dummy_handler },
{State::S4, dummy_handler },
};
assert(mapping.size() == static_cast<size_t>(State::Count)); //May use this
// line to ensume all handlers are set
auto dispatch = [&](State e) {
if (auto itr = mapping.find(e); itr != mapping.end()) {
itr->second();
}
};
auto e = State::S1; // Handlers for s1 will be called
dispatch(e);
e = State::S3;
dispatch(e);  // handler for s3
return 0;
}

如果使用编译器g++,则可以添加编译器选项-Wall或-Wswitch。例如,g++ -std=c++17 -Wall main.cppg++ -std=c++17 -Wswitch main.cpp。对于代码

#include <iostream>
enum class State{
S1,
S2
};
int main()
{
State state = State::S1;
switch (state) {
case State::S1:
std::cout << "State::S1" << std::endl;
break;
}
}

你可以得到编译错误:

main.cpp:11:12: warning: enumeration value 'S2' not handled in switch [-Wswitch]

最新更新