C++ CRTP Pattern Complete Guide
이 글의 핵심
Master C++ CRTP for static polymorphism without virtual function overhead. Complete guide with mixin counters, comparable helpers, singleton patterns, and performance comparisons.
What is CRTP?
Compile-time polymorphism and policy design connect with Strategy and PIMPL discussions in the comprehensive pattern guide, and as FAQ mentions, creation points are often considered together with Factory. Curiously Recurring Template Pattern
- Pass derived class as template argument to base class
- Static polymorphism (compile time)
- Implement polymorphism without virtual functions
// Base class
template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
// Derived class
class Derived : public Base<Derived> {
public:
void implementation() {
cout << "Derived implementation" << endl;
}
};
int main() {
Derived d;
d.interface(); // "Derived implementation"
}
Virtual Functions vs CRTP
Virtual Functions (Dynamic Polymorphism)
class Base {
public:
virtual void func() = 0;
virtual ~Base() {}
};
class Derived : public Base {
public:
void func() override {
cout << "Derived" << endl;
}
};
// Runtime overhead (vtable)
CRTP (Static Polymorphism)
template<typename Derived>
class Base {
public:
void func() {
static_cast<Derived*>(this)->funcImpl();
}
};
class Derived : public Base<Derived> {
public:
void funcImpl() {
cout << "Derived" << endl;
}
};
// Compile time, no overhead
Practical Examples
Example 1: Counter Mixin
template<typename Derived>
class Countable {
private:
static int count;
public:
Countable() { count++; }
Countable(const Countable&) { count++; }
~Countable() { count--; }
static int getCount() { return count; }
};
template<typename Derived>
int Countable<Derived>::count = 0;
class Widget : public Countable<Widget> {
public:
Widget() { cout << "Widget created" << endl; }
};
class Gadget : public Countable<Gadget> {
public:
Gadget() { cout << "Gadget created" << endl; }
};
int main() {
Widget w1, w2;
Gadget g1;
cout << "Widget count: " << Widget::getCount() << endl; // 2
cout << "Gadget count: " << Gadget::getCount() << endl; // 1
}
Example 2: Auto-Generate Comparison Operators
template<typename Derived>
class Comparable {
public:
friend bool operator!=(const Derived& lhs, const Derived& rhs) {
return !(lhs == rhs);
}
friend bool operator>(const Derived& lhs, const Derived& rhs) {
return rhs < lhs;
}
friend bool operator<=(const Derived& lhs, const Derived& rhs) {
return !(rhs < lhs);
}
friend bool operator>=(const Derived& lhs, const Derived& rhs) {
return !(lhs < rhs);
}
};
class Point : public Comparable<Point> {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
// Only implement == and <, rest auto-generated
friend bool operator==(const Point& lhs, const Point& rhs) {
return lhs.x == rhs.x && lhs.y == rhs.y;
}
friend bool operator<(const Point& lhs, const Point& rhs) {
if (lhs.x != rhs.x) return lhs.x < rhs.x;
return lhs.y < rhs.y;
}
};
int main() {
Point p1(1, 2);
Point p2(3, 4);
cout << (p1 == p2) << endl; // 0
cout << (p1 != p2) << endl; // 1
cout << (p1 < p2) << endl; // 1
cout << (p1 >= p2) << endl; // 0
}
Example 3: Singleton Mixin
template<typename Derived>
class Singleton {
protected:
Singleton() {}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Derived& getInstance() {
static Derived instance;
return instance;
}
};
class Config : public Singleton<Config> {
friend class Singleton<Config>;
private:
Config() { cout << "Config created" << endl; }
int value = 0;
public:
void setValue(int v) { value = v; }
int getValue() { return value; }
};
class Logger : public Singleton<Logger> {
friend class Singleton<Logger>;
private:
Logger() { cout << "Logger created" << endl; }
public:
void log(const string& msg) {
cout << "[LOG] " << msg << endl;
}
};
int main() {
Config::getInstance().setValue(100);
Logger::getInstance().log("System started");
cout << Config::getInstance().getValue() << endl; // 100
}
Example 4: Chaining Interface
template<typename Derived>
class Chainable {
protected:
Derived& self() {
return static_cast<Derived&>(*this);
}
};
class QueryBuilder : public Chainable<QueryBuilder> {
private:
string query;
public:
QueryBuilder& select(const string& fields) {
query = "SELECT " + fields;
return self();
}
QueryBuilder& from(const string& table) {
query += " FROM " + table;
return self();
}
QueryBuilder& where(const string& condition) {
query += " WHERE " + condition;
return self();
}
string build() {
return query;
}
};
int main() {
QueryBuilder qb;
string sql = qb.select("*")
.from("users")
.where("age > 18")
.build();
cout << sql << endl;
// SELECT * FROM users WHERE age > 18
}
Performance Comparison
#include <chrono>
// Virtual functions
class VirtualBase {
public:
virtual int compute(int x) = 0;
virtual ~VirtualBase() {}
};
class VirtualDerived : public VirtualBase {
public:
int compute(int x) override {
return x * 2;
}
};
// CRTP
template<typename Derived>
class CRTPBase {
public:
int compute(int x) {
return static_cast<Derived*>(this)->computeImpl(x);
}
};
class CRTPDerived : public CRTPBase<CRTPDerived> {
public:
int computeImpl(int x) {
return x * 2;
}
};
int main() {
const int N = 100000000;
// Virtual functions
VirtualBase* vb = new VirtualDerived();
auto start = chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
vb->compute(i);
}
auto end = chrono::high_resolution_clock::now();
cout << "Virtual: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
// CRTP
CRTPDerived cd;
start = chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
cd.compute(i);
}
end = chrono::high_resolution_clock::now();
cout << "CRTP: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
}
Typical results: CRTP is 2-5x faster than virtual functions in tight loops due to inlining and no vtable lookup.
Common Issues
Issue 1: Wrong casting
// ❌ Dangerous
template<typename Derived>
class Base {
public:
void func() {
static_cast<Derived*>(this)->impl();
}
};
class Wrong : public Base<OtherClass> { // Wrong type!
public:
void impl() {}
};
// ✅ Correct usage
class Correct : public Base<Correct> {
public:
void impl() {}
};
Issue 2: Circular dependency
// ❌ Circular dependency
class A : public Base<B> {}; // B not yet defined
class B : public Base<A> {};
// ✅ Each uses itself as template argument
class A : public Base<A> {};
class B : public Base<B> {};
Issue 3: Missing virtual destructor
// ❌ Possible memory leak
template<typename Derived>
class Base {
// No virtual destructor
};
// ✅ Add virtual destructor (for polymorphic deletion)
template<typename Derived>
class Base {
public:
virtual ~Base() = default;
};
CRTP Use Scenarios
1. Performance-critical cases
// Game engines, high-performance computing
template<typename Derived>
class Entity {
public:
void update(float dt) {
static_cast<Derived*>(this)->updateImpl(dt);
}
};
2. Code reuse
// Common functionality as mixin
template<typename Derived>
class Serializable {
public:
string serialize() {
// Serialization logic
}
};
3. Compile-time polymorphism
// Polymorphism via template argument
template<typename T>
void process(T& obj) {
obj.compute(); // Decided at compile time
}
FAQ
Q1: When to use CRTP?
A:
- Performance-critical cases
- Compile-time polymorphism needed
- Mixin patterns
Q2: Always CRTP instead of virtual functions?
A: No. Use virtual functions when runtime polymorphism needed.
Q3: CRTP disadvantages?
A:
- Increased code complexity
- Longer compile times
- No runtime polymorphism
Q4: What are mixins?
A: Pattern combining multiple base classes to add functionality.
Q5: CRTP vs Template Method pattern?
A: CRTP is static, Template Method is dynamic.
Q6: CRTP learning resources?
A:
- “Modern C++ Design” (Andrei Alexandrescu)
- “C++ Templates: The Complete Guide”
- Boost library source code
Related Articles
- C++ Policy-Based Design Complete Guide
- C++ CRTP Complete Guide | Static Polymorphism & Compile-Time Optimization
- C++ Template Template Parameters Guide
Related Posts
- C++ CRTP Complete Guide | Static Polymorphism & Compile-Time Optimization
- C++ Policy-Based Design
- C++ auto Type Deduction | Let Compiler Handle Complex Types
- C++ CTAD
- C++20 Concepts Complete Guide | New Era of Template Constraints
Keywords
C++, CRTP, static polymorphism, templates, mixins, compile-time optimization, design patterns
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ CRTP 완벽 가이드 | 정적 다형성과 컴파일 타임 최적화
- C++ Policy-Based Design | ‘정책 기반 설계’ 가이드
- C++ 컴파일 타임 프로그래밍 | constexpr·consteval·if constexpr 완벽 가이드
- C++ 디자인 패턴 종합 가이드 | Singleton·Factory
- C++ Factory Pattern 완벽 가이드 | 객체 생성 캡슐화와 확장성
- C++ Strategy Pattern 완벽 가이드 | 알고리즘 캡슐화와 런타임 교체
이 글에서 다루는 키워드 (관련 검색어)
C++, CRTP, templates, polymorphism, design patterns, metaprogramming 등으로 검색하시면 이 글이 도움이 됩니다.