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?
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();
};