How can I avoid virtual call overhead when using crtp with common interface class?

Viewed 396

I am using CRTP for performance reasons, so I have this hierarchy :

template <typename Derived>
class Base{
  public:
    double do_sthing(){
      static_cast<Derived*>(this)->do_sthing();
      }
};

class DerivedA : public Base<DerivedA>{
  public:
    inline double do_sthing(){ ... }
 };

class DerivedB : public Base<DerivedB>{
  public:
    inline double do_sthing(){ ... }
  };

But now I want to put objects of type DerivedA and DerivedB in a vector and do the following kind of code :

 for(auto obj : my_vector) {
   obj->do_sthing();
 }

my solution was to create a common interface class from which Base will inherit :

class Interface {
  virtual void do_sthing() = 0;
};

But in this case I am incurring the virtual call overhead, and I did all the CRTP to avoid it. Is there a manner to avoid that? I thought of declaring the method do_sthing in Base as final.

template <typename Derived>
class Base{
  public:
  double do_sthing() override final{
    static_cast<Derived*>(this)->do_sthing();
  }
};

Do I suffer the virtual call cost in the above? Indeed, if I use virtual methods and plain old polymorphism, my performance hit comes from the vtable look up but also from losing the inline in the case of templates

1 Answers

When you bring together different classes into a common structure known only at run time (like std::vector here), you can't avoid the virtual call overhead. This is a general C++ principle, for instance found also in std::function, std::any and so on. CRTP is no different in that respect.

Moreover CRTP (also known as static polymorphism) is primarily a tool for avoiding code repetition (without any performance overhead). It is not intended to speed up virtual function calls, because that is dynamic polymorphism stuff.

Beside this, you basically have two approaches to set up your classes:

One approach is to use a three-fold inheritance hierarchy, where I find it useful to separate the dynamic and static inheritance by different function names. Example:

struct Interface
{
     virtual ~Interface() = default;
     virtual double do_sthing_impl() = 0;
     auto do_sthing() { return this->do_sthing_impl(); }
};

template<typename Derived>
struct Base<Derived> : public Interface
{
     virtual double do_sthing_impl() const override { return this->do_sthing(); }
     auto do_sthing() { return static_cast<Derived&>(*this).do_sthing(); }
};

struct DerivedA : public Base<DerivedA>
{
     auto do_sthing() { /* ... */ }
};

Then:

std::vector<std::unique_ptr<Interface> > v;
v.push_back(std::make_unique<DerivedA>());
v.push_back(std::make_unique<DerivedB>());

Second alternative is type_erasure, e.g. via the following code (which is basically inventing an inheritance structure but without close coupling)

template<typename Derived>
struct Base{ /* ... */ };

struct DerivedA : public Base<DerivedA>{ /* ... */ };

struct type_erased
{
     virtual double do_sthing() = 0;
};

template<typename Derived>
struct type_erased_impl : public type_erased
{
     Derived d;
     type_erasure_impl(Derived d) : d(std::move(d)) {}
     virtual double do_sthing() const override { return d.do_sthing(); }
};

Then:

std::vector<std::unique_ptr<type_erased> > v;
v.push_back(std::make_unique<type_erased_impl<DerivedA> >(DerivedA{}));
v.push_back(std::make_unique<type_erased_impl<DerivedB> >(DerivedB{}));

I prefer the second alternative, because it's more flexible and doesn't introduce a fixed inheritance structure. Rather, you can implement several type erased versions of the same class where you, according to the actual need, expose only specific functions.

Related