CRTP(Curiously recurring template pattern) 소개

CRTP(Curiously recurring template pattern)

CRTP는 클래스 X가 X 자신을 템플릿 인자로 사용하여 클래스 템플릿 인스턴스화로부터 파생되게 하는 C++ 표현(Idiom, 관용구 )이다. 이 표현 이름은 1995년에 Jim Coplien에 의해 만들어 졌다.

[Wikipedia 정의]
The curiously recurring template pattern (CRTP) is an idiom in C++ in which a class X derives from a class template instantiation using X itself as template argument.[1] More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.

The technique was formalized in the 1980s as "F-bounded quantification."The name "CRTP" was independently coined by Jim Coplien in 1995, who had observed it in some of the earliest C++ template code as well as in code examples that Timothy Budd created in his multiparadigm language Leda.

1. 일반적 형식

// The Curiously Recurring Template Pattern (CRTP)
template<class T>
class Base
{
    // methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
    // ...
};

이 CRTP 패턴을 사용한 몇 가지 사용 사례들은 정적 다형성(Static Polymorphism)과 'Modern C++ Design'에 Andrei Alexandrescu에 의해 기술된 메타프로그래밍 기술들이 있다.

2. 정적 다형성(Static Polymorphism)

정적 다형성은 template을 사용하여 구현할 수 있다. 정적 의미는 모듈간 인터페이스 연결(binding)이 컴파일 때 결정되는 것을 의미하고, 정적 다형성의 의미는 컴파일 때 연결되는 인터페이스가 규정되지 않은(unbounded) 것을 의미한다. 

참고로 동적 다형성은 상속에 의해 상위 클래스에 의해 미리 설계된 인터페이스에 한정되어(bounded) 미리 결정된 것을 의미하며, 동적 의미는 실행 시간에 인터페이스가 연결(binding) 됨을 의미한다.

다음은 Wikipedia에 있는 정적 다형성의 예제다. Derived 클래스는 CRTP이 적용된 Base 클래스를 상속 받았지만, template 인자로 Derived 자신을 사용한다. Base 클래스는 정적 다형성으로 interface()와 static_func() 함수에서 template 인자의 인스턴스의 implementation()과 static_sub_func() 인터페이스가 호출하게 되는데(다형성), 만약 template 인자의 인스턴스가 이 두 인터페이스가 구현되지 않았다면 컴파일 에러(정적)가 나게 된다. 

이 기법은 동적 다형성에서 구현된 virtual functions 들의 장점들을 정적 바인등 되어 비용 없이 비슷한 효과를 낼 수 있다. CRTP 한 부분인 이런 사용을 '유사한 동적 바인딩(simulated dynamic binding)'으로 불려지게 되었는데, 이 패턴은 Windows의 ATL과 WTL 라이브러리와 boost C++ Library 등에서 광범위하게 사용되고 있다.

정적 다형성은 동적 다형성에 비해 생성된 코드가 잠재적으로 더 빠르고, 컴파일 때 모든 바인딩이 검사되기 때문에 데이터형의 안정성이 높지만, 구현 코드가 노출과 더불어 동적 바인딩에 비해 실행 코드가 커진다는 단점도 있다.

template <class T> 
struct Base
{
    void interface()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : public Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

 하지만 위 예제는 Derived::implementation() 이 public으로 규정되었다는 단점이 있다. 이 문제을 해결하기 위해서는 다음과 같이 Proxy 클래스를 사용해서 Derived::implementation() 함수를 숨길 수 있다.

template<class T> class Base
{
public:
    void interface()
    {
        return static_cast<Proxy*>(this)->implementation();
    }

    static void static_interface()
    {
        return Proxy::static_implementation();
    }

private:
    class Proxy: public T
    {
        // friend Base; /* uncomment to allow full access */
        friend void Base::interface();
        friend void Base::static_interface();
    };
};

class Derived: public Base<Derived>
{
protected:
    void implementation()
    {

    }

    static void static_implementation()
    {

    }
};

3. Reference

2. 관용구: 관용구(慣用句)는 습관으로 오랫동안 널리 사용되어 온 한 묶음의 단어 · 문구나 표현

댓글

이 블로그의 인기 게시물

macOS가 갑자기 부팅이 되지 않을 경우 데이터 복구 또는 백업 방법

C++로 프로그래밍할 때 인자 또는 리턴 값으로 std::vector 등 STL 데이터 타입 처리하는 좋은 방법

Git 저장소를 병합하는 방법(How to merge repositories in Git)