关于C++:奇怪的重复模板模式(CRTP)是什么?

What is the curiously recurring template pattern (CRTP)?

如果不参考一本书,有人能用一个代码示例为CRTP提供一个很好的解释吗?


简而言之,crtp是当类A有一个基类,它是类A本身的模板专门化。例如。

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

这是奇怪的反复出现,不是吗?:)

现在,这给了你什么?这实际上使X模板能够成为其专门化的基类。

例如,您可以创建这样的通用单例类(简化版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;

现在,为了使一个任意的类成为一个单例,您应该这样做

1
2
3
4
class A: public Singleton<A>
{
   //Rest of functionality for class A
};

你明白了吗?singleton模板假定它对任何类型x的专门化将从singleton继承,从而使它的所有(公共的、受保护的)成员都可以访问,包括GetInstance!CRTP还有其他有用的用途。例如,如果要计算当前存在于类中的所有实例,但要将此逻辑封装在一个单独的模板中(具体类的想法非常简单-有一个静态变量,以ctors递增,以dtors递减)。试着做一个练习!

另一个有用的例子是Boost(我不确定他们是如何实现它的,但是CRTP也会实现)。假设您只想为类提供operator<而自动为它们提供operator==!

你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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, isnit it?
    Derived const& d2 = static_cast<Derived const&>(op2);
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}

现在你可以这样用了

1
2
3
4
5
6
7
8
9
struct Apple:public Equality<Apple>
{
    int size;
};

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

现在,您还没有为Apple显式提供operator==但你有!你可以写

1
2
3
4
5
6
7
8
9
10
11
int main()
{
    Apple a1;
    Apple a2;

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

如果您只为Apple编写operator==的话,这似乎会减少您的编写量,但是可以想象,equality模板将不仅提供==而且提供>、>=、<=等,并且您可以将这些定义用于多个类,重用代码!

CRTP是一件很好的事情。)


这里你可以看到一个很好的例子。如果使用虚拟方法,程序将知道在运行时执行什么。实现CRTP编译器是在编译时决定的!!!!这是一场精彩的表演!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
"
, str);
    }

  private:
    FILE* mFile;
};


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

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


CRTP是一种实现编译时多态性的技术。下面是一个非常简单的例子。在下面的示例中,ProcessFoo()使用Base类接口,Base::Foo调用派生对象的foo()方法,这就是您使用虚拟方法的目的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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;
}

输出:

1
2
derived foo
AnotherDerived foo


正如注意:

CRTP可以用来实现静态多态性(比如动态多态性,但是没有虚拟函数指针表)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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;
}

输出将是:

1
2
Derived1 method
Derived2 method


这不是一个直接的答案,而是CRTP如何有用的一个例子。

CRTP的一个很好的具体例子是来自C++ 11的std::enable_shared_from_this

[util.smartptr.enab]/1

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

也就是说,从std::enable_shared_from_this继承可以在不访问实例的情况下(例如,从只知道*this的成员函数)获得指向实例的共享(或弱)指针。

当你需要给一个std::shared_ptr时,它是有用的,但是你只有访问*this的权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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));
    }
};

你不能直接通过this而不是shared_from_this()的原因是它会破坏所有权机制:

1
2
3
4
5
6
7
8
9
10
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);