개발/C++

흥미로운 반복 템플릿 패턴 (CRTP)은 무엇입니까?

MinorMan 2020. 9. 30. 22:33
반응형

<질문>

책을 참조하지 않고 코드 예제와 함께 CRTP에 대한 좋은 설명을 제공 할 수 있습니까?


<답변1>

요컨대, CRTP는 클래스 A가 클래스 A 자체에 대한 템플릿 전문화 인 기본 클래스를 갖는 경우입니다. 예

template  
class X{...};
class A : public X {...};

이상하게 되풀이되지 않나요? :)

자, 이것은 당신에게 무엇을 제공합니까? 이것은 실제로 X 템플릿에 해당 전문화에 대한 기본 클래스가 될 수있는 기능을 제공합니다.

예를 들어 다음과 같은 일반 싱글 톤 클래스 (단순 버전)를 만들 수 있습니다.

template  
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
   private:
     Singleton(){}
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template 
T* Singleton::p = nullptr;

이제 임의의 클래스 A를 싱글 톤으로 만들려면 다음을 수행해야합니다.

class A: public Singleton
{
   //Rest of functionality for class A
};

그래서 알 겠어요? 싱글 톤 템플릿은 모든 유형 X에 대한 전문화가 싱글 톤에서 상속된다고 가정합니다. 따라서 GetInstance!를 포함하여 모든 (공개, 보호) 구성원에 액세스 할 수 있습니다! CRTP의 다른 유용한 용도가 있습니다. 예를 들어, 현재 클래스에 대해 존재하는 모든 인스턴스를 계산하고 싶지만이 로직을 별도의 템플릿에 캡슐화하려는 경우 (구체적인 클래스에 대한 아이디어는 매우 간단합니다. 정적 변수, ctor에서 증가, dtor에서 감소) ). 운동으로 해보세요!

Boost의 또 다른 유용한 예입니다 (어떻게 구현했는지 잘 모르겠지만 CRTP도 그렇게 할 것입니다). 클래스에는 <연산자 만 제공하고 클래스에는 자동으로 == 연산자를 제공하고 싶다고 상상해보십시오!

다음과 같이 할 수 있습니다.

template
class Equality
{
};

template 
bool operator == (Equality const& op1, Equality const & op2)
{
    Derived const& d1 = static_cast(op1);//you assume this works     
    //because you know that the dynamic type will actually be your template parameter.
    //wonderful, isn't it?
    Derived const& d2 = static_cast(op2); 
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}

이제 이렇게 사용할 수 있습니다

struct Apple:public Equality 
{
    int size;
};

bool operator < (Apple const & a1, Apple const& a2)
{
    return a1.size < a2.size;
}

이제 Apple에 대해 연산자 ==를 명시 적으로 제공하지 않았습니까? 하지만 당신은 그것을 가지고 있습니다! 당신은 쓸 수 있습니다

int main()
{
    Apple a1;
    Apple a2; 

    a1.size = 10;
    a2.size = 10;
    if(a1 == a2) //the compiler won't complain! 
    {
    }
}

이것은 Apple에 대해 operator ==를 작성하면 덜 작성하는 것처럼 보일 수 있지만 Equality 템플릿이 ==뿐만 아니라>,> =, <= 등을 제공한다고 상상해보십시오. 여러 클래스에 대해 이러한 정의를 사용할 수 있습니다. 코드 재사용!

CRTP는 멋진 것입니다 :) HTH


<답변2>

여기에서 훌륭한 예를 볼 수 있습니다. 가상 방법을 사용하는 경우 프로그램은 런타임에서 실행되는 항목을 인식합니다. CRTP를 구현하면 컴파일러가 컴파일 시간을 결정합니다 !!! 이것은 대단한 성능입니다!

template 
class Writer
{
  public:
    Writer()  { }
    ~Writer()  { }

    void write(const char* str) const
    {
      static_cast(this)->writeImpl(str); //here the magic is!!!
    }
};


class FileWriter : public Writer
{
  public:
    FileWriter(FILE* aFile) { mFile = aFile; }
    ~FileWriter() { fclose(mFile); }

    //here comes the implementation of the write method on the subclass
    void writeImpl(const char* str) const
    {
       fprintf(mFile, "%s\n", str);
    }

  private:
    FILE* mFile;
};


class ConsoleWriter : public Writer
{
  public:
    ConsoleWriter() { }
    ~ConsoleWriter() { }

    void writeImpl(const char* str) const
    {
      printf("%s\n", str);
    }
};

<답변3>

CRTP는 컴파일 타임 다형성을 구현하는 기술입니다. 여기 아주 간단한 예가 있습니다. 아래 예제에서 ProcessFoo ()는 Base 클래스 인터페이스와 함께 작동하고 Base :: Foo는 가상 메서드로 수행하려는 작업 인 파생 객체의 foo () 메서드를 호출합니다.

http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e

template 
struct Base {
  void foo() {
    (static_cast(this))->foo();
  }
};

struct Derived : public Base {
  void foo() {
    cout << "derived foo" << endl;
  }
};

struct AnotherDerived : public Base {
  void foo() {
    cout << "AnotherDerived foo" << endl;
  }
};

template
void ProcessFoo(Base* b) {
  b->foo();
}


int main()
{
    Derived d1;
    AnotherDerived d2;
    ProcessFoo(&d1);
    ProcessFoo(&d2);
    return 0;
}

산출:

derived foo
AnotherDerived foo

<답변4>

이것은 직접적인 대답이 아니라 CRTP가 어떻게 유용 할 수 있는지에 대한 예입니다.

CRTP의 좋은 구체적인 예는 C ++ 11의 std :: enable_shared_from_this입니다.

[util.smartptr.enab] / 1 클래스 T는 enable_shared_from_this에서 상속 할 수 있습니다. * this를 가리키는 shared_ptr 인스턴스를 얻는 shared_from_this 멤버 함수를 상속합니다.

즉, std :: enable_shared_from_this에서 상속하면 인스턴스에 대한 액세스없이 공유 (또는 약한) 포인터를 얻을 수 있습니다 (예 : * this에 대해서만 알고있는 멤버 함수에서).

std :: shared_ptr을 제공해야 할 때 유용하지만 * this에만 액세스 할 수 있습니다.

struct Node;

void process_node(const std::shared_ptr &);

struct Node : std::enable_shared_from_this // CRTP
{
    std::weak_ptr parent;
    std::vector> children;

    void add_child(std::shared_ptr child)
    {
        process_node(shared_from_this()); // Shouldn't pass `this` directly.
        child->parent = weak_from_this(); // Ditto.
        children.push_back(std::move(child));
    }
};

shared_from_this () 대신 이것을 직접 전달할 수없는 이유는 소유권 메커니즘을 깨기 때문입니다.

struct S
{
    std::shared_ptr get_shared() const { return std::shared_ptr(this); }
};

// Both shared_ptr think they're the only owner of S.
// This invokes UB (double-free).
std::shared_ptr s1 = std::make_shared();
std::shared_ptr s2 = s1->get_shared();
assert(s2.use_count() == 1);

<답변5>

참고 사항 :

CRTP는 정적 다형성을 구현하는 데 사용할 수 있습니다 (동적 다형성과 비슷하지만 가상 함수 포인터 테이블이 없음).

#pragma once
#include 
template 
class Base
{
    public:
        void method() {
            static_cast(this)->method();
        }
};

class Derived1 : public Base
{
    public:
        void method() {
            std::cout << "Derived1 method" << std::endl;
        }
};


class Derived2 : public Base
{
    public:
        void method() {
            std::cout << "Derived2 method" << std::endl;
        }
};


#include "crtp.h"
int main()
{
    Derived1 d1;
    Derived2 d2;
    d1.method();
    d2.method();
    return 0;
}

출력은 다음과 같습니다.

Derived1 method
Derived2 method
반응형