콘텐츠로 이동

CRTP — Curiously Recurring Template Pattern

CRTP(Curiously Recurring Template Pattern)는 기반 클래스가 파생 클래스를 템플릿 인수로 받는 패턴입니다. 가상 함수(vtable) 없이 컴파일 타임 다형성을 구현해 런타임 오버헤드를 제거합니다.


// 기반 클래스가 파생 클래스를 템플릿 인수로 받음
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 기본 구현"
}

// 가상 함수 기반
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바이트)가 없고, 호출이 인라인될 수 있어 성능 민감 코드에 유리합니다.


// 비교 연산자 자동 생성 믹스인
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); }
};

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(); // 2
std::cout << Button::count(); // 1
// 각 파생 클래스마다 독립된 카운터

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() { /* 데이터 처리 */ }
};

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보다 간결한 대안이 됩니다.