虚拟方法开销是否显著

Is virtual methods overhead significant?

本文关键字:是否 开销 方法 虚拟      更新时间:2023-10-16

我有一些复杂的继承结构,主要是为了避免代码重复和方便各种类的通用接口。它依赖于虚拟和非虚拟继承,看起来或多或少是这样的:

class AbstractItem
{
   //bunch of abstract methods
};
class AbstractNode : virtual public AbstractItem
{
    //some more virtual abstract methods
};
class AbstractEdge : virtual public AbstractItem
{
    //yet some different virtual abstract methods
};

然后是一些像这样的"真实"类

class Item : virtual public AbstractItem
{
    //implements AbstractItem
};
class Node : public Item, public AbstractNode
{
    //implements AbstractNode
};
class Edge : public Item, public AbstractEdge
{
    //implemetns AbstractEdge
};

并将其打包到一个图形模型类中,以便:

class AbstractGraph
{
    virtual QList<AbstractNode*> nodes() const = 0;
    virtual QList<AbstractEdge*> edges() const = 0;
};
class GraphModel : public AbstractGraph
{
public:
    virtual QList<AbstractNode*> nodes() const override; //this converts m_Nodes to a list of AbstractNode*
    virtual QList<AbstractEdge*> edges() const override; //dtto
private:
    QList<Node*> m_Nodes;
    QList<Edge*> m_Edge;
};

这种复杂结构的原因是,有不同的类实现AbstractGraph,如排序模型、过滤,这些类有不同的变体-有些类按照所示的模型存储数据,并有自己的AbstractItem/Node/Edge派生类集,另一些类是动态的,依赖于底层图/模型的数据,而没有自己的数据。示例:

class FilterNode : public AbstractNode
{
    //access the data in the m_Item via AbstractItem interface and implements differently AbstractNode interface
private:
    AbstractItem *m_Item = nullptr; //this variable holds pointer to some real item with actual data such as the one from GraphModel
};
class GraphFilter : public AbstractGraph
{
    //implements the interface differently to the GraphModel
private:
    QList<FilterNode*> m_Nodes;
    AbstractGraph *m_Source = nullptr; //source graph...
};

我对此有了第二个想法,因为它依赖于(虚拟)继承,依赖于通过base等调用的抽象方法。由此产生的开销有那么大吗

另一种选择是:

a) 复制粘贴大量代码以避免虚拟方法和大部分继承,但这将是代码维护的噩梦。再加上没有通用接口。。。

b) 以某种方式将其全部模板化。。。我有点不确定,甚至不知道这是否可能。为了避免代码重复,我已经在其中的一些地方使用了它们。

那么,这看起来合理吗?还是有些过头了?我可能会补充说,在某些情况下,我会绕过虚拟调用直接调用方法(在模型内部),但在外部,它几乎总是通过抽象库进行调用。

试图用C++实现使用动态多态性的通用图算法会使事情变得

  1. 不必要的硬
  2. 不必要的慢

函数越简单,虚拟函数开销就越显著。在引用的接口中,您还可以从各种函数返回容器。即使这些是COW容器,也涉及一些工作,随意访问序列可能很容易取消共享(即复制)表示。

在遥远的过去(大约1990年至1996年),我曾尝试过一种基于动态多态性的图算法的通用实现,并一直在努力解决各种问题以使其发挥作用。当我第一次读到STL时,发现大多数问题都可以通过类似的抽象来解决(尽管仍然缺少一个关键思想:属性映射;有关详细信息,请参阅下面对BGL的引用)。

我发现用类似STL的抽象来实现图算法更可取。算法是根据特定的概念实现的函数模板,这些概念有点像基类,除了两个关键区别:

  1. 抽象中不涉及虚拟函数调用,函数通常可以内联
  2. 从函数返回的类型只需要建模一个合适的概念,而不必通过某种形式的继承来兼容某个特定的接口

无可否认,我是有偏见的,因为我的毕业论文就是关于这个主题的。对于这种方法的[独立开发]应用程序,请查看Boost图库(BGL)。

对于比较不同函数调用方法的一些性能度量,请查看函数调用基准。它们是根据性能TR中函数调用的性能测量进行建模的。