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.
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 objectsEach 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
