CRTP in C++

How to implement in C++

Usually creating a CRTP begins by defining a base class as a template, which takes the type of the derived class as a template parameter. This allows the base class to access the derived class's members and methods.

The derived class must inherit from the base template class, passing itself as a template argument. This creates recursive dependencywhere the base class can use the functionality of the derived class.

Then in the base class you can use static_cast to cast a pointer this to the type of the derived class, which allows you to call its methods directly.

Example: Object Counter

Let's create a CRTP on the class for the object counter:

Let's start by defining the base class Counter, which will take into account the number of created and still existing objects of derived classes. The base class will use CRTP, taking the derived class as a template parameter:

template<typename T>
class Counter {
public:
    Counter() {
        ++created;
        ++alive;
    }

    Counter(const Counter&) {
        ++created;
        ++alive;
    }

    ~Counter() {
        --alive;
    }

    static int howManyAlive() {
        return alive;
    }

    static int howManyCreated() {
        return created;
    }

private:
    static int created;
    static int alive;
};

template<typename T>
int Counter<T>::created(0);

template<typename T>
int Counter<T>::alive(0);

Now you can create derived classes that will inherit from Counter, passing itself as a template parameter. This will allow each derived class to have its own counters of created and existing objects:

class MyClass : public Counter<MyClass> {
    // класс с каким-то функционалом
};

class AnotherClass : public Counter<AnotherClass> {
    // еще один класс с другим функционалом
};

You can create objects of these classes and see how the counter defined in the base class works Counter:

int main() {
    MyClass a, b;
    AnotherClass c;

    std::cout << "MyClass alive: " << MyClass::howManyAlive() << std::endl;
    std::cout << "AnotherClass alive: " << AnotherClass::howManyAlive() << std::endl;

    {
        MyClass d;
        std::cout << "MyClass alive (in scope): " << MyClass::howManyAlive() << std::endl;
    }

    std::cout << "MyClass alive (out of scope): " << MyClass::howManyAlive() << std::endl;
    std::cout << "MyClass created: " << MyClass::howManyCreated() << std::endl;
    std::cout << "AnotherClass alive: " << AnotherClass::howManyAlive() << std::endl;
    std::cout << "AnotherClass created: " << AnotherClass::howManyCreated() << std::endl;

    return 0;
}

Example: statistical polymorphism

Let's define a base class Vehiclewhich will be used to implement static polymorphism via CRTP. The class will contain a method getNumberOfWheelswhich delegates the call to a derived class:

template<typename Derived>
class Vehicle {
public:
    int getNumberOfWheels() const {
        // статическое приведение к производному типу и вызов его реализации
        return static_cast<const Derived*>(this)->getNumberOfWheelsImpl();
    }
};

Let's define several derived classes, such as Car And Bicycleeach of which is inherited from Vehicle, passing itself as a template parameter. These classes will provide their own method implementation getNumberOfWheelsImpl:

class Car : public Vehicle<Car> {
public:
    int getNumberOfWheelsImpl() const {
        return 4; // автомобили обычно имеют 4 колеса
    }
};

class Bicycle : public Vehicle<Bicycle> {
public:
    int getNumberOfWheelsImpl() const {
        return 2; // у велосипедов обычно 2 колеса
    }
};

It is now possible to create vehicle objects and query the number of wheels using the single interface provided by the base class Vehicle.

int main() {
    Car myCar;
    Bicycle myBicycle;

    std::cout << "Car has " << myCar.getNumberOfWheels() << " wheels.\n";
    std::cout << "Bicycle has " << myBicycle.getNumberOfWheels() << " wheels.\n";

    return 0;
}

Common mistakes

Hiding Methods

When a derived class overrides a method declared in a CRTP base class, it is possible hiding base class method. Thus, calls to methods of the base class will not be available to objects of the derived class, even if such a call was intended.

To avoid this problem, you need to adequately plan your inheritance hierarchy and method naming to prevent unwanted method hiding. You can also use qualified method calls via this->method() to explicitly point to base class methods.

Undefined behavior when inheriting incorrectly

If a derived class incorrectly uses CRTP, i.e. inherits from Base<AnotherClass> instead of Base<DerivedClass>this may result in undefined behavior.

To prevent such errors, you can add a default constructor to the base class in a private section and make the derived class friendly.


Use CRTP only where it is really justified! CRTP is ideal for creating static polymorphism, but can add complexity to your code if used unnecessarily.

In conclusion, I would like to recommend you free lesson C++ Developer course. Professional about STL containers. Registration available via link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *