What is the curiously recurring template pattern (CRTP)?

前端 未结 5 1822
小鲜肉
小鲜肉 2020-11-21 11:27

Without referring to a book, can anyone please provide a good explanation for CRTP with a code example?

相关标签:
5条回答
  • 2020-11-21 12:04

    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);
    
    0 讨论(0)
  • 2020-11-21 12:04

    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
    
    0 讨论(0)
  • 2020-11-21 12:07

    In short, CRTP is when a class A has a base class which is a template specialization for the class A itself. E.g.

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

    It is curiously recurring, isn't it? :)

    Now, what does this give you? This actually gives the X template the ability to be a base class for its specializations.

    For example, you could make a generic singleton class (simplified version) like this

    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;
    

    Now, in order to make an arbitrary class A a singleton you should do this

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

    So you see? The singleton template assumes that its specialization for any type X will be inherited from singleton<X> and thus will have all its (public, protected) members accessible, including the GetInstance! There are other useful uses of CRTP. For example, if you want to count all instances that currently exist for your class, but want to encapsulate this logic in a separate template (the idea for a concrete class is quite simple - have a static variable, increment in ctors, decrement in dtors). Try to do it as an exercise!

    Yet another useful example, for Boost (I am not sure how they have implemented it, but CRTP will do too). Imagine you want to provide only operator < for your classes but automatically operator == for them!

    you could do it like this:

    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 <
    }
    

    Now you can use it like this

    struct Apple:public Equality<Apple> 
    {
        int size;
    };
    
    bool operator < (Apple const & a1, Apple const& a2)
    {
        return a1.size < a2.size;
    }
    

    Now, you haven't provided explicitly operator == for Apple? But you have it! You can write

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

    This could seem that you would write less if you just wrote operator == for Apple, but imagine that the Equality template would provide not only == but >, >=, <= etc. And you could use these definitions for multiple classes, reusing the code!

    CRTP is a wonderful thing :) HTH

    0 讨论(0)
  • 2020-11-21 12:16

    Here you can see a great example. If you use virtual method the program will know what execute in runtime. Implementing CRTP the compiler is which decide in compile time!!! This is a great performance!

    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);
        }
    };
    
    0 讨论(0)
  • 2020-11-21 12:17

    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
    
    0 讨论(0)
提交回复
热议问题