CRTP
CRTP (Curiously Recurring Template Pattern)
파생 클래스가 템플릿 매개변수로 자기 자신을 넘기는 패턴을 CRTP(Curiously Recurring Template Pattern, 이상하게 재귀하는 템플릿 패턴)라고 불러요. 다형성을 컴파일 타임에 구현하는 고전적인 기법이에요.
출처: cppreference
본문
CRTP는 클래스 X가 템플릿 매개변수 Z를 받는 클래스 템플릿 Y에서 파생되고, Y가 Z = X로 인스턴스화되는 관용구(idiom)예요. 예를 들어:
template<class Z>
class Y {};
class X : public Y<X> {};
이 패턴에서 X는 자기 타입으로 Y<X>를 인스턴스화하므로, Y 안에서 Z를 통해 파생 타입 X를 알아낼 수 있어요.
예제 (Example)
CRTP는 기반 클래스가 인터페이스를 노출하고 파생 클래스가 그 인터페이스를 구현하는 "컴파일 타임 다형성"을 구현하는 데 쓰일 수 있어요.
#include <cstdio>
#ifndef __cpp_explicit_this_parameter // Traditional syntax
template <class Derived>
struct Base
{
void name() { static_cast<Derived*>(this)->impl(); }
protected:
Base() = default; // prohibits the creation of Base objects, which is UB
};
struct D1 : public Base<D1> { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base<D2> { void impl() { std::puts("D2::impl()"); } };
#else // C++23 deducing-this syntax
struct Base { void name(this auto&& self) { self.impl(); } };
struct D1 : public Base { void impl() { std::puts("D1::impl()"); } };
struct D2 : public Base { void impl() { std::puts("D2::impl()"); } };
#endif
int main()
{
D1 d1; d1.name();
D2 d2; d2.name();
}
출력 (Output):
D1::impl()
D2::impl()
전통적 구문의 Base::name()은 this를 Derived*로 static_cast해서 파생 타입의 impl()을 호출해요. D1은 Base<D1>의, D2는 Base<D2>의 인스턴스라서, 같은 name()이지만 실제로는 각자의 impl()이 실행돼요. C++23의 deducing this 구문(this auto&& self)을 쓰면 기반 클래스를 템플릿화하지 않고도 같은 효과를 낼 수 있어요.
더 알아보기 (Learn more)
- 명시적 객체 멤버 함수 (deducing this) (C++23).
std::enable_shared_from_this(C++11) — 객체가 자기 자신을 가리키는shared_ptr을 만들 수 있게 해주는 클래스 템플릿.std::ranges::view_interface(C++20) — CRTP를 사용해 view를 정의하기 위한 헬퍼 클래스 템플릿.