Search code examples
c++oopcrtp

C++ CRTP pattern, constructor definitions


I'm currently playing around with the CRTP pattern in my C++ project and stumbled across the problem, that I have to redefine a constructor quiet a few times. I'm curious if my approach is wrong or if I can adjust the code so that I can define it once and all derived classes can handle it just fine.

I have the following code right now:

celestial_body.hpp:

#pragma once

#include <raylib.h>

#include <memory>
#include <string>

class CelestialBodyBase {
 public:
  CelestialBodyBase(std::string name, double mass, double radius)
      : name(name), mass(mass), radius(radius), position({0.0, 0.0, 0.0}) {}
  virtual ~CelestialBodyBase() = default;
  virtual void update() = 0;

 protected:
  std::string name;
  double mass;
  double radius;
  Vector3 position;
};

template <class T>
class CelestialBody : public CelestialBodyBase {
 public:
  CelestialBody(std::string name, double mass, double radius)
      : CelestialBodyBase(name, mass, radius) {}
  void update() { derived()->update(); }

 private:
  T* derived() { return static_cast<T*>(this); }
};

planet.hpp

#pragma once

#include <starsystem/celestial_body.hpp>

class Planet : public CelestialBody<Planet> {
  friend class CelestialBody<Planet>;

 public:
  Planet(std::string name, double mass, double radius);
  ~Planet();
  void update();
};

planet.cpp

#include <starsystem/planet.hpp>

Planet::Planet(std::string name, double mass, double radius)
    : CelestialBody(name, mass, radius) {}

Planet::~Planet() {}

void Planet::update() {
  // Update planet
  // ...
}

As you can see, there is a constructor which is basically the same for all 3 classes being defined here. Mind that I have the CelestialBodyBase class to make it compatible with STL containers. I kinda expected that I could just define a constructor in for example the base, and the derived classes inherit that. Does the pattern look right this way, can I change something to make it less tedious to type out?


Solution

  • I kinda expected that I could just define a constructor in for example the base, and the derived classes inherit that.

    The syntax for that is using Base::Base;. That brings the Base constructor into the current scope.

    template <class T>
    class CelestialBody : public CelestialBodyBase {
     public:
    
      // Base constructor is also valid here.
      using CelestialBodyBase::CelestialBodyBase;
    
      void update() { derived()->update(); }
    
     private:
      T* derived() { return static_cast<T*>(this); }
    };
    
    
    class Planet : public CelestialBody<Planet> {
      friend class CelestialBody<Planet>;
    
     public:
    
      // Base constructor is also valid here.
      using CelestialBody::CelestialBody;
    
      ~Planet();
      void update();
    };