프로그래밍 일반/객체 지향

이상한 재귀 템플릿 패턴(Curiously Recurring Template Pattern, CRTP)

지노윈 2019. 10. 27. 21:35
반응형

이 패턴의 아이디어는 자기 자신을 부모 클래스의 템플릿 인자로 상속 받는 것이예요.

자기 자신을 템플릿 인자로 상속을 받는다고요? 헉~ 참 이상하면서도 자기 자신을 부모 클래스의 템플릿 인자로 하였으니 재귀 템플릿을 붙였나 봐요. 그래서 이상한 재귀 템플릿이라 부르죠. 작명참 잘 한 듯~ 줄여서 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에서는 이 패턴이 즐겨 사용되지요.

성능을 요하는 프레임워크에서도 이 패턴이 당연히 즐겨 사용되겠지요.