0Pricing
C++ Academy · 강의

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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. CRTP 관용구
  2. 정적 다형성
  3. CRTP를 사용한 믹스인
  4. CRTP를 사용할 시점
← C++ Academy(으)로 돌아가기