是否有任何设计模式可以避免嵌套开关盒?

时间:2013-02-12 07:56:43

标签: c++ design-patterns switch-statement

我见过类似的线程,但是,不知道如何将解决方案完全应用到我的案例中。我的问题是,我有一套用例可以说'A'' B' C' 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;
}
....

}

我正在考虑使用(pair,command)的地图并删除这个开关案例,但有没有其他更好的解决方案或设计问题来解决这个问题?

6 个答案:

答案 0 :(得分:8)

如果性能这是一个大问题,那么函数指针的映射可能是一个解决方案。

假设标签ABC ...是小于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
    };
    
  • 使用map为每组输入调用适当的处理程序:

     dispatcher[KEY(input1,input2)] (/* args */);
    

请注意,您必须使用每对可能的输入设置调度程序。此外,如果KEY(A,B)KEY(B,A)对是相同的情况,那么您可以编写一个名为invoke的函数来处理这种情况,以便为其余代码提供统一的用法。< / p>

 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!

希望有所帮助。

答案 1 :(得分:2)

在你的情况下,如何将两个开关分成两个功能

bool processInput2(char input2)
{
  switch(input2)
  {
   case 'A':
   {  
      // blah
   }
    break;
}

bool processInput1(char input1)
{
  switch(input1)
  {
   case 'A':
      processInput2(input2);
      break;
}

答案 2 :(得分:1)

一种可能性是将代码拆分为每个嵌套大小写的一个函数,因此您的示例将具有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;
...

要调用相应的函数,请写:

f[input1][input2]();

请注意,通过使用C ++ 11 std::function类型,函数不必是经典函数指针;它们也可以是lambda函数或函子对象。

您还可以将某些部分保留为空或多次分配相同的功能。当你决定保留一些条目为空(所以在这种情况下不应该做任何事情),在调用之前检查函数对象:

if (f[input1][input2])
    f[input1][input2]();

答案 3 :(得分:0)

您可以随时执行以下操作:

switch ( 256 * input1 + input2 ) {
case 256 * 'A' + 'B':
    //  ...
    break;
//  ...
};

但坦率地说,在这种情况下,我会发现嵌套开关更容易 理解,假设 switch是正确的答案 你的问题。对于字符输入,它通常是,但有 其他替代方案,例如std::map<std::pair<char, char>, Action const*>,其中Action是虚拟基类,以及 映射中的每个操作都是派生类的静态实例。 这具有使每个动作成为不同对象的优点 (这可能不是一个优势,取决于你在做什么 动作),如果地图是动态填充的(例如 在Action)的构造函数中,您可以不添加操作 修改解析器的源代码(但您可能不需要 这种灵活性)。

答案 4 :(得分:0)

建议的答案与地图或指针处理功能表是可以的。但我看到两个缺点: 1)与手动嵌套交换机相比,性能略有下降。 2)案件处理方法不是完全自我描述的。我的意思是你必须两次提到每个句柄方法 - 在它的'定义和你初始化地图的地方。

我看到两个备选方案: 1)源代码生成。从某种表示自动生成嵌套开关。嗯...如果不介意为这么小的任务添加代码生成,那么创建最佳代码是非常好的选择。 2)使用预处理器黑客。不是最优雅但非常有趣的方式使它发挥作用。

首先我们为枚举声明X-Macro

#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
}

这里做了很多努力来“欺骗”预处理器以递归方式扩展ELEMENTS。主要观点有很好的描述here

现在我们将处理程序声明为模板函数特化:

template <Elements Element1, Elements Element2>
void process();

template<>
void process<Elements::firstElement, Elements::firstElement>()
{
    //some code 1;
}

...

答案 5 :(得分:-1)

为什么不使用分支机构?

if (input1 == A && input2 == B) {
} else if (input1==A && input2 = C) {
} ...

这是写你的意思。