C++ 是否有任何设计模式可以避免嵌套开关案例?
我见过类似的线程,但不确定如何将解决方案准确地应用到我的案例中。我的问题是,我有一组用例,比如说‘a’、‘B’、‘C’,当传递的输入(2个用例是输入)是列出的任何2个用例时,我需要执行某些命令。例如:C++ 是否有任何设计模式可以避免嵌套开关案例?,c++,design-patterns,switch-statement,C++,Design Patterns,Switch Statement,我见过类似的线程,但不确定如何将解决方案准确地应用到我的案例中。我的问题是,我有一组用例,比如说‘a’、‘B’、‘C’,当传递的输入(2个用例是输入)是列出的任何2个用例时,我需要执行某些命令。例如: switch(input1) { case A: break; case B: break; case C: break; } 在每个案例中,我必须检查输入2, 因此,最终的代码可能看起来像 switch(input1) { case A: { switch(input2): case B: b
switch(input1)
{
case A:
break;
case B:
break;
case C:
break;
}
在每个案例中,我必须检查输入2,
因此,最终的代码可能看起来像
switch(input1)
{
case A:
{
switch(input2):
case B:
break;
case c:
break;
}
case B:
{
switch(input2):
case A:
break;
case c:
break;
}
....
}
我想使用(对、命令)的映射并删除此开关盒,但是否有其他更好的解决方案或设计问题来解决此问题?在您的情况下,将两个开关分成两个功能如何
bool processInput2(char input2)
{
switch(input2)
{
case 'A':
{
// blah
}
break;
}
bool processInput1(char input1)
{
switch(input1)
{
case 'A':
processInput2(input2);
break;
}
为什么不使用if分支
if (input1 == A && input2 == B) {
} else if (input1==A && input2 = C) {
} ...
这就是你的意思。如果性能不是一个大问题,那么函数指针的映射可能是一种解决方案
假设标签A
,B
,C
。。。是小于255
的小整数值
- 首先设置地图
#define KEY(a,b) ( (a<<8) | b ) std::map<int, function_pointer_type> dispatcher = { { KEY(A,B), ab_handler}, { KEY(A,C), ac_handler}, { KEY(B,C), bc_handler}, //etc };
键(A,B)
和键(B,A)
是相同的情况,那么您可以编写一个名为invoke
的函数来处理这种情况,以便为其余代码提供统一的用法
void invoke(int input1, int input2, /* args */)
{
if (dispatcher.find(KEY(input1, input2)) != dispatcher.end() )
dispatcher[KEY(input1,input2)] (/* args */);
else
dispatcher[KEY(input2,input1)] (/* args */);
}
然后将其用作:
invoke(input1, input2, /* args */);
invoke(input2, input1, /* args */); //still okay!
希望这会有所帮助。一种可能性是将代码拆分为每个嵌套案例的一个函数,这样您的示例将有6个函数:
void process_A_A() { ... }
void process_A_B() { ... }
void process_B_A() { ... }
void process_B_B() { ... }
void process_C_A() { ... }
void process_C_B() { ... }
然后,在初始化时将它们放入数组中,以便在运行时进行快速(恒定时间)查找:
typedef std::function<void(void)> Function; // or: void (*)(void)
Function f[Input1Count][Input2Count];
f[A][A] = &process_A_A;
f[A][B] = &process_A_B;
...
注意,通过使用C++11std::function
类型,函数不必是经典的函数指针;它们也可以是lambda函数或函子对象
您还可以将某些部分保留为空或多次分配相同的功能。当您决定将某些条目保留为空(因此在这种情况下不应执行任何操作)时,请在调用函数对象之前检查它:
if (f[input1][input2])
f[input1][input2]();
您可以随时执行以下操作:
switch ( 256 * input1 + input2 ) {
case 256 * 'A' + 'B':
// ...
break;
// ...
};
但坦率地说,在这种情况下,我会发现嵌套开关更容易
要理解,假设开关
是
你的问题。对于字符输入,通常是这样,但也有
其他替代方法,例如std::map
,其中Action
是虚拟基类,以及
映射中的每个操作都是派生类的静态实例。
这样做的好处是使每个动作成为一个不同的对象
(这可能不是一个优势,取决于您在
操作),并且如果地图是动态填充的(例如
在Action
的构造函数中,可以添加不带
修改解析器的源代码(但您可能不需要
这种灵活性)。建议使用映射或指针表来处理函数的答案是可以的。但我看到了两个缺点: 1) 与手动嵌套交换机相比,性能略有下降。 2) 案例处理方法不是完全自描述性的。我的意思是你必须两次提到每个句柄方法——在它的定义中,在你初始化映射的地方 我看到两个备选方案: 1) 源代码生成。从某种表示自动生成嵌套开关。好。。。如果您不介意为这么小的任务添加代码生成,那么创建最佳代码是一个非常好的选择。 2) 使用预处理器黑客。这不是最优雅但很有趣的方法 首先,我们为我们的枚举声明:
#define ELEMENTS(processor) \
processor(firstElement) \
processor(secondElement) \
processor(thirdElement)
我们可以使用它来声明枚举本身:
#define ENUM_PROCESSOR(arg) arg,
enum class
{
ELEMENTS(ENUM_PROCESSOR)
};
#undef ENUM_PROCESSOR
Now we can add method that uses macros to generate nested switches:
void processElements(const Elements element1,
const Elements element2)
{
// These macros are useful to trick the preprocessor to allow recursive macro calls
// https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
#define EMPTY(...)
#define DEFER(...) __VA_ARGS__ EMPTY()
#define EXPAND(...) __VA_ARGS__
#define ELEMENTS_INT() ELEMENTS
#define PROCESS_NESTED_ENUM_VALUE(value) \
case Elements::value: \
{ \
process<Value1, Elements::value>(); \
break; \
}
#define PROCESS_ENUM_VALUE(value) \
case Elements::value: \
{ \
constexpr Elements Value1 = Elements::value; \
switch (element2) \
{ \
DEFER(ELEMENTS_INT)()(PROCESS_NESTED_ENUM_VALUE) \
}; \
\
break; \
}
switch (element1)
{
EXPAND(ELEMENTS(PROCESS_ENUM_VALUE));
};
#undef EMPTY
#undef DEFER
#undef EXPAND
#undef ELEMENT_TYPES_INT
#undef PROCESS_ENUM_VALUE
#undef PROCESS_NESTED_ENUM_VALUE
}
#定义枚举处理器(arg)arg,
枚举类
{
元素(枚举处理器)
};
#未定义枚举处理器
现在,我们可以添加使用宏生成嵌套开关的方法:
void processElements(常量元素element1,
常量元素(元素2)
{
//这些宏用于欺骗预处理器以允许递归宏调用
// https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks-提示-和习语
#定义空(…)
#定义延迟(…)\uuuu VA\u参数\uuuuu空()
#定义扩展(…)\uuu VA\u参数__
#定义元素\u INT()元素
#定义进程\嵌套\枚举\值(值)\
案例元素::值:\
{ \
过程()\
中断\
}
#定义进程枚举值(值)\
案例元素::值:\
{ \
constexpr Elements Value1=元素::值\
开关(元件2)\
{ \
延迟(元素)(进程嵌套枚举值)\
}; \
\
中断\
}
开关(元件1)
{
展开(元素(进程枚举值));
};
#未定义为空
#未定义延迟
#未定义扩展
#未定义元素类型
#未定义进程枚举值
#未定义进程\u嵌套\u枚举\u值
}
这里做了很多工作来“欺骗”预处理器以递归方式扩展元素。主要思想描述得很好
现在,我们将处理程序声明为模板函数专用化:
template <Elements Element1, Elements Element2>
void process();
template<>
void process<Elements::firstElement, Elements::firstElement>()
{
//some code 1;
}
...
模板
无效过程();
模板
无效过程()
{
//一些代码1;
}
...
#define ENUM_PROCESSOR(arg) arg,
enum class
{
ELEMENTS(ENUM_PROCESSOR)
};
#undef ENUM_PROCESSOR
Now we can add method that uses macros to generate nested switches:
void processElements(const Elements element1,
const Elements element2)
{
// These macros are useful to trick the preprocessor to allow recursive macro calls
// https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
#define EMPTY(...)
#define DEFER(...) __VA_ARGS__ EMPTY()
#define EXPAND(...) __VA_ARGS__
#define ELEMENTS_INT() ELEMENTS
#define PROCESS_NESTED_ENUM_VALUE(value) \
case Elements::value: \
{ \
process<Value1, Elements::value>(); \
break; \
}
#define PROCESS_ENUM_VALUE(value) \
case Elements::value: \
{ \
constexpr Elements Value1 = Elements::value; \
switch (element2) \
{ \
DEFER(ELEMENTS_INT)()(PROCESS_NESTED_ENUM_VALUE) \
}; \
\
break; \
}
switch (element1)
{
EXPAND(ELEMENTS(PROCESS_ENUM_VALUE));
};
#undef EMPTY
#undef DEFER
#undef EXPAND
#undef ELEMENT_TYPES_INT
#undef PROCESS_ENUM_VALUE
#undef PROCESS_NESTED_ENUM_VALUE
}
template <Elements Element1, Elements Element2>
void process();
template<>
void process<Elements::firstElement, Elements::firstElement>()
{
//some code 1;
}
...