문제

What is the best way to call virtual functions in a derived class so that the compiler can inline or otherwise optimize the call?

Example:

class Base {
  virtual void foo() = 0;
};

class Derived: public Base {
  virtual void foo() {...}
  void bar() {
    foo();
  }
};

I want the call to foo() in bar() to always call Derived::foo(). It is my understanding that the call will result in a vtable lookup and the compiler cannot optimize it out since there may be another class inheriting from Derived.

I could explicitly call Derived::foo() but that gets verbose if there are many virtual function calls in Derived. I also find it surprising that I could not find much material online addressing what seems to me to be a common case (a 'final' derived class calling virtual methods) so I wonder if I am misusing virtual functions here or excessively optimizing.

How should this be done? Stop prematurely optimizing and stick with foo(), suck it up and use Derived::foo(), or is there a better way?

도움이 되었습니까?

해결책

C++11 contains the final keyword, which "specifies that a virtual function can not be overridden in a derived class or that a class cannot be inherited from."1.

It appears that g++ is able to optimize the virtual function call in the derived class if it has been declared final.

I created the following test:

virtualFunctions.h

#pragma once
class Base {
public:
  virtual void foo();
  virtual void bar();
  virtual void baz();
  int fooVar, barVar, bazVar;
};
class Derived: public Base {
public:
  void test();
  virtual void foo();
  virtual void bar();
  virtual void baz() final;
};

virtualFunctions.cpp:

#include "virtualFunctions.h"
void Derived::test() {
  foo();
  Derived::bar();
  baz();
}
void Derived::foo() {
  fooVar = 101;
}
void Derived::bar() {
  barVar = 202;
}
void Derived::baz() {
  bazVar = 303;
}

I am using g++ 4.7.2 and with -O1 the generated assembly contains:

_ZN7Derived4testEv:
.LFB0:
    .loc 1 3 0
    .cfi_startproc
.LVL3:
    pushl   %ebx
.LCFI0:
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    subl    $24, %esp
.LCFI1:
    .cfi_def_cfa_offset 32
    movl    32(%esp), %ebx      ; Load vtable from the stack
    .loc 1 4 0
    movl    (%ebx), %eax        ; Load function pointer from vtable
    movl    %ebx, (%esp)
    call    *(%eax)             ; Call the function pointer
.LVL4:
    .loc 1 5 0
    movl    %ebx, (%esp)
    call    _ZN7Derived3barEv   ; Direct call to Derived::bar()
.LVL5:
    .loc 1 6 0
    movl    %ebx, (%esp)
    call    _ZN7Derived3bazEv   ; Devirtualized call to Derived::baz()

Derived::bar() and Derived::baz() were both called directly, while the vtable was used for foo().

다른 팁

The compiler may be able to optimize it and perform devirtualization if it can statically find out what type is used.

Virtual method calls are quite cheap. Sometime ago I read an article stating that the overhead is roughly ten percent compared to a normal method call. This of course does not consider the missing inlining opportunity.

I also have a feeling that this mixes interface and implementation. I think it would be better to split it into a pure interface and an implementation class.

As you yourself say, the performance impact of this should be your concern only in extreme rare cases. If you are compiling as C++11 you can declare Derived and/or foo()/bar() as final and the compiler might inline it.

The answer to the question is to disable dynamic dispatch, and that can be done through qualification:

class Derived: public Base {
  virtual void foo() {...}
  void bar() {
    Derived::foo();          // no dynamic dispatch
  }
};

Now the question is whether this is going to make a difference in performance (measure before changing things!) and whether it makes sense to do this. A virtual function is an extension point for derived types. If you disable dynamic dispatch, someone might create MoreDerived, implement foo and expect that bar calls MoreDerived::foo, but if you disabled dynamic dispatch that won't happen.

Unless there is a really good, measured, reason to try to micro-optimize this, avoid the problem altogether. Chances are that if you run your code in a profiler the dynamic dispatch is not going to show up at all.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top