Question

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?

Was it helpful?

Solution

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().

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top