ABOUT ME

Email : haejin.yang91@gmail.com

Today
Yesterday
Total
  • CRTP는 어떻게 가능할까?
    분석과탐구 2021. 10. 13. 03:36

     링크 : CRTP(Curiously recurring template pattern)

     

    Curiously recurring template pattern - 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 a template argument.[1] More generally it is known as F-bound polymorphism, and it is a form of F-bounded qu

    en.wikipedia.org

    CRTP라는 코드 구성이 어떻게 가능한지 살펴볼 예정이고, 효과, 사용법은 위 링크를 참고하길 바란다.

     

    CRTP란?

     C++ CRTP는 다음과 같이 쓰는 형식을 말한다. 

    template <typename T>
    class Base
    {
    public:
    private:
    };
    
    class Derived : public Base<Derived>
    {
    public:
    private:
    };

    CRTP는 자기 자신을 템플릿 인자로 사용하는 패턴인 것이다.  처음 이 코드를 봤을 때 이해가 안 갔었다. "아니 어떻게 자기 자신을 템플릿 인자로 받아서 클래스를 만들 수 있지?"  컴파일러는 어떻게 이런 일을 가능하게 만드는지 궁금해졌다.

    도대체 이게 어떻게 가능한거야?

    CRTP와 같은 코드가 가능한 이유 

    CRTP가 어떻게 가능한지 찾아보았다. 검색어는 "CRTP how is this possible?". 다행히 나랑 비슷한 사람이 스택오버플로우에 질문을 올렸다. 이 질문의 대답은 "Instantiating Base for Derived does not require Derived to be complete, only to know that it is a class type."이었다. 그러니까 템플릿 인자에 들어가는 클래스가 complete할 필요는 없다는 것이다. 여기서 complete하다는 것은 정의를 가진 것으로 추측하고 있다. 정의는 컴파일러가 기계어를 만들기 위한 구체적인 코드를 의미한다. 그런데, complete하지 않아도 된다고 하였으니, 선언만 있어도 되는 것으로 보인다.

     

    그렇다면 선언과 정의는 무슨 차이인가? 깊숙히 들어가면 CRTP에서 벗어나니 간단하게만 살펴보자.

     

    // 선언만 있는 코드
    extern int a; // a변수에 대한 정의는 다른 곳에 있다.
    
    // 선언과 정의 모두 있는 코드
    int a = 10;
    
    // 선언만 있는 클래스
    class a;
    
    // 선언과 정의가 같이 있는 클래스
    class a
    {
    public:
    private:
    }
    
    // int a = 10;처럼 선언과 정의를 함께하는 경우가 대부분이라,
    // 선언과 정의를 엄밀하게 구별하지 않고 포괄적으로 이해하고 있다.

     

     선언과 정의의 차이에 대해선 이정도로 정리하고, 정말 템플릿 인자에 incomple type이 들어와도 되는 것이 공식적인 것인지 찾아보았다. 문서는 cppreference 사이트를 참고하였다.

     

    https://en.cppreference.com/w/cpp/language/template_parameters

    공식 문서를 해석해 보니, 템플릿 인자는 type-id이기만 하면 되고, incomplete type일 수도 있음을 나타낸다. 위 문서의 "struct A"는 정의가 없지만, main에서 사용되고 있다. 그렇다면 type-id인 것은 무엇인가?

     

    https://en.cppreference.com/w/cpp/language/type#Type_naming

     

    클래스, 공용체, 열거체 ... 등이 해당되며, 선언으로 존재하기만 하면 된다는 것이다. 그렇다면 여기서 마지막 의문이 생긴다. 컴파일러가 왼쪽부터 해석하여 class Derived를 먼저 만나서 선언이라 인식하고, 그다음에 public Base<Derived>로 진행하는 것인가?

     

    왼쪽 먼저? 오른쪽 먼저? 오른쪽부터 해석된다면 역시 Derived가 type-id인지 모르잖아?

     

    그런데, 생각해보니 CRTP 때문에 머리가 혼동되어서 헷갈린 것이지 당연해보인다. 예를 들어 이런 코드를 보자.

    template <typename T>
    class Base
    {
    public:
    private:
    };
    
    // class A; //첫 번째 위치
    
    class Derived : public Base<A>
    {
    public:
    private:
    };
    
    // class A; //두 번째 위치

    만약 class A; 라는 선언이 두 번째 위치에 있다면, public Base<A>에서 A라는 type-id를 찾지 못하니까 컴파일 오류가 발생한다. 즉, 왼쪽 위부터 오른쪽 아래로 컴파일러가 코드를 해석하는 게 당연한 것인데, 뜬금없이 의문을 가진 것이었다.....

     

    CRTP 결론

    그냥 문득 궁금해서 찾아본건데 생각보다 시간이 많이 걸렸다. 찾고나니 찾을만했다.

     

    댓글

CRTP란?


CRTP와 같은 코드가 가능한 이유 


CRTP 결론


Designed by Tistory.