Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual function performance when called by derived classes?

Is there a performance penalty when a virtual method is called from a class that's known to be the derived class at compile time? Below I explicitly call force_speak with a derived class.

Code:

#include <iostream>
#include <array>
#include <memory>

class Base
{
public:
  virtual void speak()
  {
    std::cout << "base" << std::endl;
  }
};

class Derived1 : public Base
{
public:
  void speak()
  {
    std::cout << "derived 1" << std::endl;
  }
};

template<class B>
void force_speak(std::array<std::unique_ptr<B>, 3>& arr)
{
  for (auto& b: arr)
  {
    b->speak();
  }
}

int main()
{
  std::array<std::unique_ptr<Derived1>, 3> arr = 
    {
      std::unique_ptr<Derived1>(new Derived1),
      std::unique_ptr<Derived1>(new Derived1),
      std::unique_ptr<Derived1>(new Derived1)
    };
  force_speak(arr);
  return 0;
}
like image 872
Agrim Pathak Avatar asked Dec 17 '14 21:12

Agrim Pathak


1 Answers

Is there a performance penalty when a virtual method is called from a class that's known to be the derived class at compile time? See code below.

It depends. Most compilers will "de-virtualize" code like this:

Derived1 d;
d.speak();

The dynamic type of the object is known at the call site, so the compiler can avoid going through the vtable to make the call and can just call Derived1::speak() directly.

In your example the compiler needs to be smarter because in force_speak you only have Derived1* pointers (stored inside the unique_ptr objects) and in that context it's not clear whether the dynamic type of the pointed-to objects is Derived1 or some more-derived type. The compiler either needs to inline the call to force_speak into main (where the dynamic type is known) or use some additional knowledge about the type to allow devirtualization to happen. (As one example of additional knowledge, whole program optimization can determine that there are no other derived types declared anywhere in the program, so a Derived1* must point to a Derived1.)

Using the C++11 final keyword can help compilers to devirtualize some cases, e.g. if Derived1 is marked final then the compiler knows that a Derived1* can only point to a Derived1 and not some other type derived from it that might override speak()

like image 193
Jonathan Wakely Avatar answered Nov 01 '22 09:11

Jonathan Wakely