IT story

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

hot-time 2020. 5. 26. 07:48
반응형

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


책을 언급하지 않고 누구나 CRTP코드 예제 통해 좋은 설명을 제공 할 수 있습니까?


간단히 말해서, CRTP는 A클래스에 클래스 A자체를 위한 템플릿 전문인 기본 클래스가있는 경우입니다 . 예 :

template <class T> 
class X{...};
class A : public X<A> {...};

그것은 입니다 호기심, 그렇지 반복? :)

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

예를 들어, 다음과 같이 일반 싱글 톤 클래스 (간체 버전)를 만들 수 있습니다

template <class ActualClass> 
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 <class T>
T* Singleton<T>::p = nullptr;

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

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

알 겠어요? 싱글 톤 템플릿은 모든 유형에 대한 전문성이 있다고 가정 X에서 상속됩니다 singleton<X>포함, 멤버 접근 (보호가 공공) 모든이있을 것이다, 따라서와 GetInstance! CRTP의 다른 유용한 용도가 있습니다. 예를 들어, 클래스에 현재 존재하는 모든 인스턴스를 세고 싶지만이 로직을 별도의 템플릿으로 캡슐화하려는 경우 (구체적인 클래스에 대한 아이디어는 매우 간단합니다. 정적 변수, ctor 단위로 증가, dtors 감소) ). 운동으로 해보십시오!

Boost에 대한 또 다른 유용한 예 (어떻게 구현했는지는 확실하지 않지만 CRTP도 그렇게합니다). <클래스 에만 연산자를 제공하고 자동으로 연산자 를 제공한다고 가정 하십시오 ==!

당신은 이렇게 할 수 있습니다 :

template<class Derived>
class Equality
{
};

template <class Derived>
bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2)
{
    Derived const& d1 = static_cast<Derived const&>(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<Derived const&>(op2); 
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}

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

struct Apple:public Equality<Apple> 
{
    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하지만, 상상 Equality템플릿뿐만 아니라 제공 할 수 ==있지만 >, >=, <=등 그리고 당신은 이러한 정의를 사용할 수있는 여러 코드를 재사용, 클래스!

CRTP는 훌륭한 것입니다 :) HTH


여기 좋은 예가 있습니다. 가상 메소드를 사용하면 프로그램이 런타임에서 실행되는 것을 알 수 있습니다. 컴파일 시간을 결정하는 컴파일러는 CRTP를 구현합니다 !!! 이것은 훌륭한 성능입니다!

template <class T>
class Writer
{
  public:
    Writer()  { }
    ~Writer()  { }

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


class FileWriter : public Writer<FileWriter>
{
  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<ConsoleWriter>
{
  public:
    ConsoleWriter() { }
    ~ConsoleWriter() { }

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

CRTP is a technique to implement compile-time polymorphism. Here's a very simple example. In the below example, ProcessFoo() is working with Base class interface and Base::Foo invokes the derived object's foo() method, which is what you aim to do with virtual methods.

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

template <typename T>
struct Base {
  void foo() {
    (static_cast<T*>(this))->foo();
  }
};

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

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

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


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

Output:

derived foo
AnotherDerived foo

Just as note:

CRTP could be used to implement static polymorphism(which like dynamic polymorphism but without virtual function pointer table).

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

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


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


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

The output would be :

Derived1 method
Derived2 method

This is not a direct answer, but rather an example of how CRTP can be useful.


A good concrete example of CRTP is std::enable_shared_from_this from C++11:

[util.smartptr.enab]/1

A class T can inherit from enable_­shared_­from_­this<T> to inherit the shared_­from_­this member functions that obtain a shared_­ptr instance pointing to *this.

That is, inheriting from std::enable_shared_from_this makes it possible to get a shared (or weak) pointer to your instance without access to it (e.g. from a member function where you only know about *this).

It's useful when you need to give a std::shared_ptr but you only have access to *this:

struct Node;

void process_node(const std::shared_ptr<Node> &);

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

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

The reason you can't just pass this directly instead of shared_from_this() is that it would break the ownership mechanism:

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

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

참고URL : https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp

반응형