CRTP 관용구
기묘하게 반복되는 템플릿 패턴
CRTP 관용구은(는) CoddyKit의 무료 C++ Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C++ Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C++ Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
CRTP란 무엇인가요
Curiously Recurring Template Pattern(CRTP)은 템플릿을 Derived 자체로 인스턴스화한 기본 클래스를 Derived 클래스가 상속하는 C++ 관용구입니다.
- 형태:
class Derived : public Base<Derived> - 기본 클래스가 컴파일 시점에 파생 유형을 알게 됩니다.
template <typename T>
class Base {};
class Derived : public Base<Derived> {};핵심 메커니즘
기본 클래스 내부에서 this 포인터를 파생 유형으로 static_cast할 수 있습니다. 파생 유형이 템플릿 매개변수이므로 런타임 비용이 전혀 없는 완전한 컴파일 시점 변환입니다.
#include <iostream>
template <typename T>
class Base {
public:
void interface() {
static_cast<T*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
void implementation() { std::cout << "Derived impl\n"; }
};
int main() {
Derived d;
d.interface();
return 0;
}왜 "Curiously Recurring"일까요
이름은 겉보기에 역설적인 상황에서 유래했습니다. 기본 클래스가 파생 클래스를 참조하지만, 기본 템플릿을 작성할 때는 파생 클래스가 아직 완전히 정의되지 않았기 때문입니다.
- 템플릿은 사용될 때만 인스턴스화되므로 이 방식이 작동합니다.
- 인스턴스화할 때는
Derived가 완전한 유형입니다.
첫 번째 실제 사용 사례
CRTP를 사용하면 기본 클래스가 재사용 가능한 기능을 제공하면서 파생 클래스를 호출하도록 할 수 있습니다. 여기서 기본 클래스는 파생 클래스의 name()에 처리를 위임하는 print()를 제공합니다.
#include <iostream>
#include <string>
template <typename T>
struct Named {
void print() {
std::cout << static_cast<T*>(this)->name() << "\n";
}
};
struct Cat : Named<Cat> {
std::string name() { return "Cat"; }
};
int main() {
Cat c;
c.print();
return 0;
}가상 테이블 없음
런타임 다형성과 달리 CRTP에는 virtual 키워드가 필요하지 않으며 각 객체에 가상 테이블 포인터도 필요하지 않습니다. 디스패치가 전적으로 컴파일 시점에 해결됩니다.
- 더 작은 객체(숨겨진 vptr 없음)
- 호출을 인라인할 수 있음
인스턴스 수 세기
CRTP의 고전적인 활용 사례는 객체 카운터입니다. 각 파생 클래스가 자동으로 고유한 정적 카운터를 갖습니다.
#include <iostream>
template <typename T>
struct Counter {
static inline int count = 0;
Counter() { ++count; }
~Counter() { --count; }
};
struct Widget : Counter<Widget> {};
int main() {
Widget a, b;
std::cout << Widget::count << "\n";
return 0;
}각 파생 클래스가 고유한 상태를 가집니다
Counter<Widget>과 Counter<Gadget>은 서로 다른 템플릿 인스턴스화이므로 각각 독립적인 정적 count를 가집니다. 템플릿이 아닌 단일 기본 클래스에서는 이러한 분리가 불가능합니다.
#include <iostream>
template <typename T>
struct Counter {
static inline int count = 0;
Counter() { ++count; }
};
struct A : Counter<A> {};
struct B : Counter<B> {};
int main() {
A a1, a2;
B b1;
std::cout << A::count << " " << B::count << "\n";
return 0;
}여기서는 static_cast가 안전합니다
기본 클래스 내부의 하향 변환은 Base<T> 유형의 모든 객체가 실제로 T인 경우에만 안전합니다. T가 상속하는 클래스이므로 CRTP가 이를 구조적으로 보장합니다.
- 서로 관련 없는 클래스에 대해
Base<Wrong>을 인스턴스화하지 마세요.
CRTP로 비교 연산자 만들기
비교 도우미를 상속하면 하나의 operator<로 전체 비교 연산자 집합을 생성할 수 있습니다.
#include <iostream>
template <typename T>
struct Comparable {
friend bool operator>(const T& a, const T& b) { return b < a; }
friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};
struct Num : Comparable<Num> {
int v;
Num(int x) : v(x) {}
friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};
int main() {
std::cout << (Num(3) > Num(2)) << "\n";
return 0;
}보호된 생성자로 오용 방지하기
기본 클래스의 우발적인 오용을 방지하려면 protected 생성자를 지정하여 파생 클래스만 기본 클래스를 생성할 수 있게 하세요.
#include <iostream>
template <typename T>
class Base {
protected:
Base() = default;
public:
void run() { static_cast<T*>(this)->step(); }
};
class Job : public Base<Job> {
public:
void step() { std::cout << "step\n"; }
};
int main() {
Job j;
j.run();
return 0;
}표준 라이브러리의 CRTP
CRTP는 실제 C++ 라이브러리 전반에서 사용됩니다.
std::enable_shared_from_this<T>가 이를 사용합니다.- 많은 표현식 템플릿 수학 라이브러리(Eigen)가 이를 기반으로 합니다.
- 범위와 반복자 도우미도 유사한 패턴을 사용합니다.
X : Base<X> 형태를 알아보면 고급 코드를 읽는 데 도움이 됩니다.
빠른 확인
CRTP 형태에 대한 이해도를 확인해 보세요.
복습
CRTP 관용구를 배웠습니다.
Derived : Base<Derived>는 기본 클래스에 구체적인 유형을 제공합니다.static_cast<T*>(this)를 사용하면 컴파일 시점 디스패치가 가능합니다.- 가상 테이블이 없어 객체가 더 작고 호출을 인라인할 수 있습니다.
- 카운터, 비교 혼합 클래스 및 라이브러리 도우미에 사용됩니다.
자주 묻는 질문
“CRTP 관용구” 강의는 무료인가요?
네 — “CRTP 관용구” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C++ Academy 강의 전체를 잠금 해제할 수 있습니다. C++ Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“CRTP 관용구”에서 뭘 배우나요?
기묘하게 반복되는 템플릿 패턴 브라우저에서 직접 실행하는 실습 코드로 C++ Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C++ Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C++ Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“CRTP 관용구” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C++ Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C++ Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- CRTP 관용구
- 정적 다형성
- CRTP를 사용한 믹스인
- CRTP를 사용할 시점