이상한 재귀 템플릿 패턴(Curiously Recurring Template Pattern, CRTP)
이 패턴의 아이디어는 자기 자신을 부모 클래스의 템플릿 인자로 상속 받는 것이예요.
자기 자신을 템플릿 인자로 상속을 받는다고요? 헉~ 참 이상하면서도 자기 자신을 부모 클래스의 템플릿 인자로 하였으니 재귀 템플릿을 붙였나 봐요. 그래서 이상한 재귀 템플릿이라 부르죠. 작명참 잘 한 듯~ 줄여서 CRTP로 흔히 불러요. 저 또한 그냥 CRTP로 불러요.
이제 예제를 보면서 CRTP를 자세히 살펴 볼게요.
vtable이 있어서 런타임에 부모 클래스 타입으로 메서드를 호출해도 실제로 인스턴싱된 자식의 메서드가 실행되지요. 이 기능이 바로 다형성이죠. 같은 클래스 내에서 메서드 오버로드, 상속 관계에서 오버라이 이 모두를 객체 지향의 다형성이라 합니다. 객체 지향 설계에서는 주로 상속 관계에서의 오버라이 구현이 주로 사용되므로 퉁 쳐서 상속에 의한 다형성을 지칭하는 경우가 많습니다.
아래 코드를 쭈욱 살펴 보세요.
class Window
{
public:
void MsgLoop()
{
OnClick();
}
virtual void OnClick() { cout << "Window OnClick" << endl; }
};
class MyWindow : public Window
{
public:
void OnClick() override { cout << "MyWindow OnClick" << endl; }
};
int main()
{
MyWindow w;
w.MsgLoop();
return 0;
}
코드가 참 쉽죠? w.MsgLoop()를 호출하면 "MyWindow OnClick"이 출력됩니다.
virtual 함수로 선언하면 vtable을 만들고 key, value 방식으로 호출 하므로 오버헤드가 있겠지요.
다음 코드도 위와 동일하게 동작합니다.
template <typename TYPE>
class Window
{
public:
void MsgLoop()
{
(static_cast<type*>(this))->OnClick();
}
void OnClick() { cout << "Window OnClick" << endl; }
};
class MyWindow : public Window<MyWindow>
{
public:
void OnClick() { cout << "MyWindow OnClick" << endl; }
};
int main()
{
MyWindow w;
w.MsgLoop();
return 0;
}
두 구현의 차이는 CRTP를 사용하면 컴파일 타임에 올바른 자식을 호출 할 수 있도록 됩니다.
컴파일 타임에 이러한 것이 결정되니 당연히 성능의 이점이 있겠지요.
그래서 Meta programming에서는 이 패턴이 즐겨 사용되지요.
성능을 요하는 프레임워크에서도 이 패턴이 당연히 즐겨 사용되겠지요.