CRTP — Curiously Recurring Template Pattern
CRTP(Curiously Recurring Template Pattern)는 기반 클래스가 파생 클래스를 템플릿 인수로 받는 패턴입니다. 가상 함수(vtable) 없이 컴파일 타임 다형성을 구현해 런타임 오버헤드를 제거합니다.
1. 기본 구조
섹션 제목: “1. 기본 구조”// 기반 클래스가 파생 클래스를 템플릿 인수로 받음template<typename Derived>class Base{public: void interface() { // Derived의 구현을 정적으로 호출 static_cast<Derived*>(this)->implementation(); }
// 기본 구현 (오버라이드 가능) void implementation() { std::cout << "Base 기본 구현\n"; }};
class ConcreteA : public Base<ConcreteA>{public: void implementation() { std::cout << "ConcreteA 구현\n"; }};
class ConcreteB : public Base<ConcreteB>{ // implementation() 미정의 → Base 기본 구현 사용};
int main(){ ConcreteA a; a.interface(); // "ConcreteA 구현" — 가상 함수 없음
ConcreteB b; b.interface(); // "Base 기본 구현"}2. 가상 함수 vs CRTP 성능
섹션 제목: “2. 가상 함수 vs CRTP 성능”// 가상 함수 기반struct VirtualAnimal { virtual void speak() = 0; void do_speak() { speak(); } // vtable 간접 호출};
// CRTP 기반template<typename T>struct CRTPAnimal { void do_speak() { static_cast<T*>(this)->speak(); // 인라인 가능, 직접 호출 }};
struct Dog : CRTPAnimal<Dog> { void speak() { std::cout << "Woof\n"; }};CRTP는 vtable 포인터(8바이트)가 없고, 호출이 인라인될 수 있어 성능 민감 코드에 유리합니다.
3. 믹스인 — 기능 주입
섹션 제목: “3. 믹스인 — 기능 주입”// 비교 연산자 자동 생성 믹스인template<typename T>struct Comparable{ friend bool operator!=(const T& a, const T& b) { return !(a == b); } friend bool operator> (const T& a, const T& b) { return b < a; } friend bool operator<=(const T& a, const T& b) { return !(b < a); } friend bool operator>=(const T& a, const T& b) { return !(a < b); }};
struct Point : Comparable<Point>{ int x, y; // == 와 < 만 정의하면 나머지 4개 자동 생성 bool operator==(const Point& o) const { return x == o.x && y == o.y; } bool operator< (const Point& o) const { return x < o.x || (x == o.x && y < o.y); }};4. 인스턴스 카운터
섹션 제목: “4. 인스턴스 카운터”template<typename T>class Counter{ static inline int count_ = 0;public: Counter() { ++count_; } ~Counter() { --count_; } static int count() { return count_; }};
class Widget : public Counter<Widget> {};class Button : public Counter<Button> {};
Widget w1, w2;Button b1;
std::cout << Widget::count(); // 2std::cout << Button::count(); // 1// 각 파생 클래스마다 독립된 카운터5. 정책 기반 설계와 결합
섹션 제목: “5. 정책 기반 설계와 결합”template<typename Derived, typename LogPolicy>class Service : public LogPolicy{public: void execute() { LogPolicy::log("실행 시작"); static_cast<Derived*>(this)->run(); LogPolicy::log("실행 완료"); }};
struct ConsoleLog { void log(const char* msg) { std::cout << msg << '\n'; }};
struct FileLog { void log(const char* msg) { /* 파일 기록 */ }};
class DataService : public Service<DataService, ConsoleLog>{public: void run() { /* 데이터 처리 */ }};6. C++23 대안 — Deducing this
섹션 제목: “6. C++23 대안 — Deducing this”C++23에서는 this 매개변수를 명시적으로 선언해 CRTP를 더 간결하게 대체할 수 있습니다.
struct Base { // Deducing this로 파생 타입 추론 template<typename Self> void interface(this Self& self) { self.implementation(); }
void implementation() { std::cout << "Base\n"; }};
struct Derived : Base { void implementation() { std::cout << "Derived\n"; }};
Derived d;d.interface(); // "Derived" — 템플릿 파라미터 없이 동작CRTP는 성능 민감한 라이브러리(STL, Eigen, Boost)에서 광범위하게 사용됩니다. 가상 함수가 필요 없는 정적 다형성, 믹스인, 카운터, 인터페이스 강제에 사용하세요. C++23 Deducing this가 컴파일러에서 지원된다면 CRTP보다 간결한 대안이 됩니다.