18

我了解动态/静态多态性取决于应用程序设计和要求。但是,如果可能的话,是否总是选择静态多态性而不是动态多态性?特别是,我可以在我的应用程序中看到以下 2 种设计选择,这两种选择似乎都被建议不要使用:

  1. 使用 CRTP 实现静态多态性:没有 vtable 查找开销,同时仍以模板基类的形式提供接口。但是,使用大量的 switch 和 static_cast 来访问正确的类/方法,这是危险的

  2. 动态多态性:实现接口(纯虚拟类),将查找成本与访问器/突变器等微不足道的函数关联起来

我的应用程序对时间非常关键,所以我赞成静态多态性。但是需要知道使用过多的 static_cast 是否表明设计不佳,以及如何在不产生延迟的情况下避免这种情况。

编辑:感谢您的洞察力。举一个具体的案例,其中哪一个是更好的方法?

class IMessage_Type_1
{
  virtual long getQuantity() =0;
...
}
class Message_Type_1_Impl: public IMessage_Type_1
{
  long getQuantity() { return _qty;}
...
}

或者

template <class T>
class TMessage_Type_1
{
  long getQuantity() { return static_cast<T*>(this)->getQuantity(); }
...
}
class Message_Type_1_Impl: public TMessage_Type_1<Message_Type_1_Impl>
{
  long getQuantity() { return _qty; }
...
}

请注意,每个类中有几个修改器/访问器,我确实需要在我的应用程序中指定一个接口。在静态多态中,我只切换一次——获取消息类型。但是,在动态多态性中,我使用虚函数进行每个方法调用。这不是使用静态多边形的理由吗?我相信 CRTP 中的 static_cast 非常安全并且没有性能损失(编译时间限制)?

4

4 回答 4

15

静态和动态多态性旨在解决不同的问题,因此很少有两种情况都适用。在这种情况下,动态多态性将带来更灵活和更易于管理的设计。但大多数时候,出于其他原因,选择将是显而易见的。

两者的一个粗略分类:虚函数允许对一个通用接口进行不同的实现;模板允许不同的接口用于通用实现。

于 2012-03-28T12:20:08.407 回答
9

切换只不过是一系列跳转,经过优化后,跳转到通过表查找的地址。就像虚函数调用一样。

如果必须根据类型进行跳转,则必须先选择类型。如果无法在编译时进行选择(主要是因为它取决于输入),则必须始终执行两个操作:选择和跳转。您用于选择的语法工具不会改变性能,因为优化相同。

事实上,您正在重新发明v-table。

于 2012-03-28T13:00:33.847 回答
6

您会看到与纯粹基于模板的多态性相关的设计问题。虽然看起来虚拟的基类可以让您很好地了解派生类的预期,但在大量模板化的设计中,这变得更加困难。可以通过在使用其中一个 boost 库时引入语法错误来轻松证明这一点。

另一方面,您担心使用虚函数时会出现性能问题。证明这将是一个问题要困难得多。

恕我直言,这是一个非问题。除非另有说明,否则请坚持使用虚拟功能。虚函数调用比大多数人想象的要快得多(从动态链接库调用函数也增加了一层间接性。似乎没有人想到这一点)。

我只会考虑模板化设计,如果它使代码更易于阅读(通用算法),您使用已知的虚拟函数(数字算法)很慢的少数情况之一,或者您已经将其确定为性能瓶颈。

于 2012-03-28T12:11:11.833 回答
2

如果调用的方法可以由编译器内联,则静态多态可以提供显着的优势。例如,如果虚拟方法如下所示:

protected:
virtual bool is_my_class_fast_enough() override {return true;}

那么静态 polimophism 应该是首选方式(否则,该方法应该是诚实的并返回 false :)。

“真正的”虚拟呼叫(在大多数情况下)不能内联。

其他差异(例如 vtable 调用中的额外间接性)可以忽略不计

[编辑]

但是,如果您真的需要运行时多态性(如果调用者不应该知道方法的实现,因此不能在调用者端内联该方法),那么不要重新发明 vtable(正如 Emilio Garavaglia 提到的),只需使用它.

于 2012-03-28T14:44:37.580 回答