C++ 是否有任何设计模式可以避免嵌套开关案例?

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

我见过类似的线程,但不确定如何将解决方案准确地应用到我的案例中。我的问题是,我有一组用例,比如说‘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:
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
    };
    
请注意,您必须使用每对可能的输入设置dispatcher。另外,如果对
键(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++11
std::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;
}

...