Fundamentals 11 min read

Beyond Static Polymorphism: 3 Practical CRTP Uses – Object Counting, Interface Injection, and Cloning

The article explains CRTP’s core mechanism—each template instantiation creates an independent type—and demonstrates three real‑world patterns (object counting, interface injection, and a type‑safe clone) with complete C++ code, pitfalls, and performance comparisons to virtual‑function alternatives.

IT Services Circle
IT Services Circle
IT Services Circle
Beyond Static Polymorphism: 3 Practical CRTP Uses – Object Counting, Interface Injection, and Cloning

Understanding CRTP

CRTP (Curiously Recurring Template Pattern) works by having a derived class inherit from a base class that takes the derived type as a template argument. Each instantiation of the base template yields a distinct class with its own static members, allowing the base to know exactly which derived type is being used at compile time.

Use Case 1: Object Counting – Automatic Instance Counters

Scenario

When managing resources such as connection pools or debugging object lifetimes, you often need to know how many live instances of a particular type exist.

template <typename T>
class ObjectCounter {
    static size_t count_;
protected:
    ObjectCounter()               { ++count_; }
    ObjectCounter(const ObjectCounter&) { ++count_; }
    ObjectCounter(ObjectCounter&&) noexcept { ++count_; }
    ~ObjectCounter()              { --count_; }
public:
    static size_t alive() { return count_; }
};

template <typename T> size_t ObjectCounter<T>::count_ = 0;

class Connection : public ObjectCounter<Connection> { /* ... */ };
class Widget     : public ObjectCounter<Widget>     { /* ... */ };

// Usage
size_t c = Connection::alive(); // number of live Connection objects
size_t w = Widget::alive();     // number of live Widget objects

Each ObjectCounter<T> has its own static size_t count_, so the counters are completely independent. The implementation is not thread‑safe; replace size_t with std::atomic<size_t> for concurrent environments. Also, the move constructor must increment the count because the moved‑from object remains alive until destruction.

Use Case 2: Interface Injection – Auto‑Generating Comparison Operators

Scenario

When a type defines operator== and operator<, the remaining four relational operators are boiler‑plate code that can be generated automatically.

template <typename Derived>
class Comparable {
public:
    friend bool operator!=(const Derived& a, const Derived& b) { return !(a == b); }
    friend bool operator>( const Derived& a, const Derived& b) { return b < a; }
    friend bool operator<=(const Derived& a, const Derived& b) { return !(b < a); }
    friend bool operator>=(const Derived& a, const Derived& b) { return !(a < b); }
};

class Temperature : public Comparable<Temperature> {
    double value_;
public:
    explicit Temperature(double v) : value_(v) {}
    bool operator==(const Temperature& o) const { return value_ == o.value_; }
    bool operator<(const Temperature& o) const  { return value_ <  o.value_; }
};

The Temperature class now has a full set of six comparison operators without writing any extra code. Real‑world examples include boost::totally_ordered<T> and the standard library’s std::enable_shared_from_this<T>, which rely on the same CRTP technique to inject functionality based on the derived type.

Compared with a virtual‑function approach, the CRTP version eliminates the v‑table indirection and allows the compiler to inline all calls, which can be significant for operators that are invoked millions of times in tight loops.

Use Case 3: Clone Pattern – Type‑Safe Deep Copy

Scenario

In a polymorphic hierarchy you often need a virtual clone() that returns a copy of the concrete derived object without knowing its exact type.

class Shape {
public:
    virtual Shape* clone() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    Circle* clone() const override { return new Circle(*this); }
};

class Square : public Shape {
public:
    Square* clone() const override { return new Square(*this); }
};

Each derived class repeats the same cloning logic. CRTP removes this duplication:

template <typename Derived>
class Cloneable : public Shape {
public:
    Shape* clone() const override {
        return new Derived(static_cast<const Derived&>(*this));
    }
};

class Circle : public Cloneable<Circle> { double radius_; public: explicit Circle(double r) : radius_(r) {} };
class Square : public Cloneable<Square> { double side_;   public: explicit Square(double s) : side_(s)   {} };

Now every new derived class automatically gets a correct clone() implementation. A static_assert can guard against misuse where a class inherits from the wrong Cloneable<Derived> specialization, catching the error at compile time.

Takeaways

All three patterns rely on the same CRTP insight: each template instantiation creates an independent static state, enabling the base class to know “who is inheriting me”. While C++23’s deducing this can replace CRTP in some simple cases, it cannot provide independent static members needed for object counting, so CRTP remains a fundamental tool for modern C++ template programming.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CTemplate MetaprogrammingObject CountingClone PatternCRTPInterface InjectionStatic Polymorphism
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.