动态绑定还是开关/案例?

时间:2010-04-21 08:31:03

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

这样的场景:
我有不同的对象执行与各自func()实现类似的操作 func_manager()有两种解决方案可以根据不同的对象调用func()

解决方案1 ​​:使用c ++中指定的虚函数字符。 func_manager的工作方式不同于不同的对象点传入。

class Object{
  virtual void func() = 0;
}
class Object_A : public Object{
  void func() {};
}
class Object_B : public Object{
  void func() {};
}
void func_manager(Object* a)
{
   a->func();
}

解决方案2 :使用普通开关/外壳。 func_manager的工作方式不同于

中的不同类型传递
typedef enum _type_t
{
  TYPE_A,
  TYPE_B
}type_t;

void func_by_a()
{
// do as func() in Object_A
}
void func_by_b()
{
// do as func() in Object_A
}
void func_manager(type_t type)
{
    switch(type){
        case TYPE_A:
            func_by_a();
            break;
        case TYPE_B:
            func_by_b();
        default:
            break;
    }
}

我的问题是2:
1.在设计模式的观点,哪一个更好?
2.在 RUNTIME EFFCIENCE 的观点,哪一个更好?特别是当对象的种类增加时,总共可能达到10-15,哪一个的开销超过另一个?我不知道switch / case如何实现内部,只是一堆if / else?

非常感谢!

6 个答案:

答案 0 :(得分:7)

  从设计模式的角度来看哪一个更好?

使用多态(解决方案1)更好 只有一个数据点:想象一下,你有一个围绕两者中的任何一个构建的庞大系统,然后突然 需要添加另一种类型 。使用解决方案一,您 添加一个派生类 ,确保它在需要的地方实例化,然后您就完成了。使用解决方案2,您在整个系统中都有 成千上万的{{​​1}}语句 ,并且或多或少无法保证您找到了所有必需的地方修改它们为新类型。

  从RUNTIME EFFCIENCE的观点来看,哪个更好?特别是Object的种类

这很难说 我记得Stanley Lippmann的 Inside the C ++ Object Model 中的一个脚注,他说研究表明虚拟函数可能对切换类型有一个小优势。然而,我会很难引用一章和一节,并且,IIRC,优势似乎不足以使决定依赖于它。

答案 1 :(得分:3)

第一种解决方案更好,只因为它的代码更短。它也更容易维护和编译更快:如果你想添加类型,你只需要添加新类型作为头和编译单元,而无需更改和重新编译负责类型映射的代码。实际上,这段代码是由编译器生成的,并且可能比您自己编写的任何代码都高效或高效。

最低级别的虚函数的成本不超过表(数组)中的额外取消引用。但是不要紧,在这个微观数字上,这种性能挑剔真的无关紧要。您的代码更简单,这才是最重要的。 C ++为您提供的运行时调度是有原因的。使用它。

答案 2 :(得分:1)

使用“dynamic dispatch”或“虚拟调度”(即调用虚函数)在设计(保持一个地方的变化,可扩展性)和运行时效率方面都更好(简单的取消引用vs.一个简单的解引用,其中使用了一个跳转表或一个if ...... else的梯子,它真的很慢)。稍微说一下,“动态绑定”并不意味着你的想法......“动态绑定”是指根据最近的声明解析变量的值,而不是“静态绑定”或“词法绑定”,指的是通过声明它的当前最内部范围来解析变量。

设计
如果另一个程序员出现谁无法访问您的源代码并想要创建Object的实现,那么该程序员被卡住...扩展功能的唯一方法是在一个非常长的switch语句中添加另一个案例。对于虚函数,程序员只需要从您的接口继承并提供虚拟方法的定义,而walla,它的工作原理。此外,那些switch语句最终到处都是,因此添加新实现几乎总是需要在任何地方修改许多switch语句,而继承将更改本地化为一个类。

效率
动态调度只是在对象的虚拟表中查找函数,然后跳转到该位置。它非常快。如果switch语句使用跳转表,则速度大致相同;但是,如果实现很少,一些程序员会试图使用if ... else梯形图而不是switch语句,因为它通常不能利用跳转表,因此速度较慢。 / p>

答案 3 :(得分:1)

  1. 我会说第一个更好。在解决方案2中,func_manager必须知道所有类型,并在每次添加新类型时进行更新。如果您使用解决方案1,稍后可以添加新类型,func_manager将起作用。

  2. 在这个简单的例子中,我实际上猜测解决方案1会更快,因为它可以直接查找vtable中的函数地址。如果你有15种不同的类型,那么switch语句可能不会以跳转表的形式结束,但基本上就像你说的if / else语句一样。

答案 4 :(得分:1)

从设计的角度来看,第一个肯定是更好的,因为继承的目的是使不同的对象表现得一致。

从效率的角度来看,在两种替代方案中,您或多或少都有相同的生成代码,某些地方必须有选择制作代码。区别在于继承在第一个中自动为您处理它,而您在第二个中手动处理它。

答案 5 :(得分:1)

为什么没有人建议功能对象?我认为kingkai有兴趣解决这个问题,而不仅仅是两个解决方案 我对他们没有经验,但是他们完成了自己的工作:

struct Helloer{
    std::string operator() (void){
        return std::string("Hello world!");
    }
};

struct Byer{
    std::string operator() (void){
        return std::string("Good bye world!");
    }
};

template< class T >
void say( T speaker){
    std::cout << speaker() << std::endl;
}

int main()
{
   say( Helloer() );
   say( Byer() );
}

编辑:在我看来,这是比单一方法(不是函数调用操作符)的类更“正确”的方法。实际上,我认为这个重载被添加到C ++中以避免这样的类 此外,即使您不想要模板,功能对象也更方便使用 - 就像通常的功能一样 最后考虑STL - 它在任何地方使用func对象,看起来很自然。我甚至没有提到Boost