CRTP vs. virtual function as an interface or mixin

1.3k Views Asked by At

I wonder if there is any benefit of using CRTP over virtual function polymorphism if I never invoke the function from the base class (i.e., virtual dispatch)?

Here are the sample code. The disassembly can be found at https://godbolt.org/z/WYKaG5bbG.

struct Mixin {
  virtual void work() = 0;
};

template <typename T>
struct CRTPMixin {
  void call_work() {
    static_cast<T*>(this)->work();
  }
};

struct Parent {};
struct Child : Parent, Mixin, CRTPMixin<Child> {
  int i = 0;
  void work() override {
    i ++;
  }
};

Child child;
Mixin& mixin = child;


int main() {
  child.work();
  mixin.work();
  child.call_work();
}

I found that if I call the virtual function work from the child or through the CRTPMixin interface, the disassembly code are the same, with only static call. If I call the function on Mixin& mixin = child the virtual dispatch occurs and there are more instructions generated for this operation.

My question is, if I am designing the interface/mixin type struct, which I will only call with the derived class, not the base class, is there any case where CRTP will benefit more than the virutal function method?

Thanks!

2

There are 2 best solutions below

3
On BEST ANSWER

If you will always call from derived class only, then CRTP is much better than virtual functions. Not only it is faster to call functions directly, than through virtual dispatch, but it also allow function inlining and other optimizations.

And starting with C++23 we can do CRTP even simpler than before. example from https://en.cppreference.com/w/cpp/language/crtp

#include <cstdio>
 
#ifndef __cpp_explicit_this_parameter // Traditional syntax
 
template <class Derived>
struct Base { void name() { (static_cast<Derived*>(this))->impl(); } };
struct D1 : public Base<D1> { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base<D2> { void impl() { std::puts("D2::impl()"); } };
 
void test()
{
    Base<D1> b1; b1.name();
    Base<D2> b2; b2.name();
    D1 d1; d1.name();
    D2 d2; d2.name();
}
 
#else // C++23 alternative syntax; https://godbolt.org/z/KbG8bq3oP
 
struct Base { void name(this auto& self) { self.impl(); } };
struct D1 : public Base { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base { void impl() { std::puts("D2::impl()"); } };
 
void test()
{
    D1 d1; d1.name();
    D2 d2; d2.name();
}
 
#endif
 
int main()
{
    test();
}
0
On

I recently gained some experience when developing a project. The example I gave in the question indeed did not show the difference of using CRTP and virtual function. However, there are some cases where virtual function can not really matches the CRTP static polymorphisms' performance.

Let's say I have a Base which has an invoke public API and its behavior can be modified by implementing the run method in a derived class.

The code is shown below:

#include <iostream>

using namespace std;

/* CRTP */
template<class Derived>
struct CRTPBase {
  void run() {
    static_cast<Derived *>(this)->run();
  }

  void invoke() {
    this->run();
  }
};

struct CRTPDerived : public CRTPBase<CRTPDerived> {
  void run() {
    cout << "RUN\n";
  }

  void invoke() {
    this->run();
  }
};

/* virtual */
struct VirtualBase {
  virtual void run() {};

  void invoke() {
    this->run();
  }
};

struct VirtualDerived : public VirtualBase {
  void run() override {
    cout << "RUN\n";
  }
};

int main() {
  CRTPDerived cd;
  cd.invoke();
  VirtualDerived vd;
  vd.invoke();
}

Disassembly is available at https://godbolt.org/z/rnxd9r6Kr

In this case, even if we invoke the method on a concrete object VirtualDerived rather than using the pointer or reference of the VirtualBase, virutal dispatching still occurs and it takes four more instructions to invoke the invoke function. In this case there is no way to avoid the overhead when using virtual function.