关于oop:基类的几个级别会降低c ++中的类/结构吗?

关于oop:基类的几个级别会降低c ++中的类/结构吗?

Does several levels of base classes slow down a class/struct in c++?

具有多个级别的基类会降低一类的速度吗? A派生B派生C派生D派生F派生G,...

多重继承会降低一个类的速度吗?


根据c ++的口头禅,非虚拟函数调用在运行时绝对不会降低性能,因为您不应该为不使用的函数付费。
在虚拟函数调用中,无论有多少继承级别或具有多少基类,通常都需要进行额外的指针查找。
当然,这是所有实现的定义。

编辑:如其他地方所述,在某些多继承方案中,在进行调用之前需要对" this"指针进行调整。 Raymond Chen描述了它如何用于COM对象。基本上,在从多个基继承的对象上调用虚拟函数可能需要额外的减法和在虚拟调用所需的额外指针查找之上的jmp指令。


[深层继承层次结构]通过增加不必要的复杂性而大大增加了维护负担,甚至在用户只想使用特定的派生类时,也迫使用户学习许多类的接口。通过将不必要的vtable和间接添加到不需要它们的类中,它也会对内存使用和程序性能产生影响。如果发现自己经常创建深层次的继承层次结构,则应查看设计风格,以了解是否养成了这种不良习惯。很少需要深层次结构,而且几乎从来都不是好的。而且,如果您不相信而是认为" OO就是没有大量继承的OO",那么可以考虑的一个很好的反例是[C ++]标准库本身。 -草药萨特


  • 多重继承会变慢吗
    一类?

如多次提到的,深度嵌套的单个继承层次结构不应对虚拟调用施加任何额外的开销(高于对任何虚拟调用施加的开销)。

但是,当涉及多重继承时,通过基类指针调用虚拟函数时,有时会产生很小的额外开销。在这种情况下,某些实现会使虚函数经过一个小的调整,从而调整" this"指针,因为

1
(static_cast<Base*>( this) == this)

根据对象布局不一定是正确的。

请注意,所有这些都非常非常依赖于实现。

请参阅Lippman的" C ++对象模型内部"第4.2章-MI下的虚拟成员函数/虚拟函数


在OP的示例中,几乎所有答案都指向虚拟方法是否会变慢,但我认为OP只是在询问拥有多个继承级别本身是否较慢。答案当然不是,因为这一切都是在C ++中的编译时发生的。我怀疑这个问题是源于脚本语言的经验,在脚本语言中此类继承图可以是动态的,在这种情况下,它可能会更慢。


虚拟调用本身比普通调用更耗时,因为它必须查找实际函数的地址才能从vtable进行调用

此外,由于查找要求,内联等编译器优化可能很难执行。本身无法进行内联的情况可能会由于堆栈弹出,推入和跳转操作而导致相当高的开销

这是一个适当的研究,它说开销可能高达50%http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf

这是另一种资源,探讨了拥有大型虚拟类库的副作用http://keycorner.org/pub/text/doc/kde-slow.txt

具有多个继承的虚拟调用的分派是特定于编译器的,因此在这种情况下,实现也将起作用。

关于没有大量基类的特定问题,通常,一个类对象的内存布局将在其中包含所有其他组成类的vtbl ptrs。

查看此页面以获取示例vtable布局-http://www.codesourcery.com/public/cxx-abi/cxx-vtable-ex.html

因此,对由层次结构更深的类实现的方法的调用仍将只是一个间接调用,而不是您似乎认为的多个间接调用。调用不必从一个类导航到另一个类,以最终找到要调用的确切函数。

但是,如果您使用组合而不是继承,则每个指针调用将是一个虚拟调用,并且会产生开销,并且如果在该虚拟调用中该类使用更多的组合,则将进行更多的虚拟调用。根据您拨打的电话数量,这种设计会比较慢。


不同级别的虚拟调用之间没有速度差异,因为它们都被压平到了vtable中(指向被覆盖方法的大多数派生版本)。因此,当inst是B的实例时调用((A *)inst)-> Method()与inst是D的实例时开销相同。

现在,虚拟调用比非虚拟调用更昂贵,但这是因为指针取消引用,而不是取决于类层次结构实际深度的函数。


当子对象的每次创建导致对所有父代构造函数的调用一直到基础为止时,在深层继承树中使用非平凡的构造函数会减慢对象的创建速度。


如果没有虚函数,那么就不应该。如果有的话,调用虚拟函数会对性能产生影响,因为这些虚拟函数是通过函数指针或其他间接方法(取决于具体情况)来调用的。但是,我认为影响与继承层次结构的深度无关。

布莱恩,请澄清并回答您的评论。如果继承树中的任何地方都没有虚拟函数,那么不会影响性能。


正如Corey Ross所指出的,对于任何叶子派生类,vtable在编译时都是已知的,因此,虚拟调用的代价实际上应该是相同的,而与层次结构无关。

但是,对于dynamic_cast不能这样说。如果考虑如何实现dynamic_cast,则基本方法是在整个层次结构中进行O(n)搜索!

对于多继承层次结构,您还要付出很小的代价在层次结构中的不同类之间进行转换:

1
2
3
4
5
6
7
8
9
10
11
12
sturct A { int i; };
struct B { int j; };

struct C : public A, public B { int k ; };

// Let's assume that the layout of C is:  { [ int i ] [ int j ] [int k ] }

void foo (C * c) {
  A * a = c;                // Probably has zero cost
  B * b = c;                // Compiler needed to add sizeof(A) to 'c'
  c = static_cast<B*> (b);  // Compiler needed to take sizeof(A)' from 'b'
}

调用虚拟函数比调用非虚拟函数要慢一些。但是,我认为您的继承树有多深并不重要。

但这通常不是您应该担心的差异。


是的,如果您这样引用它:

1
2
3
4
5
// F is-a E,
// E is-a D and so on

A* aObject = new F();
aObject->CallAVirtual();

然后,您正在使用指向A类型对象的指针。给定您要调用的虚拟函数,它必须查找函数表(vtable)以获取正确的指针。有一些开销,是的。


尽管我不太确定,但我认为除非您使用虚拟方法,否则编译器应该能够很好地对其进行优化,以至于继承不会有太大的关系。

但是,如果要在基类中调用函数,而在基类中调用函数,依此类推,则可能会影响性能。

从本质上讲,它很大程度上取决于您如何构造继承树。


推荐阅读