什么是奇怪的重复模板模式(CRTP)?

Alo*_*ave 173 c++ templates c++-faq crtp

没有参考书,任何人都可以CRTP用代码示例提供一个很好的解释吗?

Arm*_*yan 265

简而言之,CRTP是指A类具有基类,它是A类本身的模板特化.例如

template <class T> 
class X{...};
class A : public X<A> {...};
Run Code Online (Sandbox Code Playgroud)

奇怪的经常性的,不是吗?:)

现在,这给你带来了什么?这实际上使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;
Run Code Online (Sandbox Code Playgroud)

现在,为了使任意A类成为单身,你应该这样做

class A: public Singleton<A>
{
   //Rest of functionality for class A
};
Run Code Online (Sandbox Code Playgroud)

所以你看?单例模板假定它的任何类型X的特化将继承A,因此可以访问其所有(公共,受保护)成员,包括A!CRTP还有其他有用的用途.例如,如果你想要计算你的类当前存在的所有实例,但是想要将这个逻辑封装在一个单独的模板中(具体类的想法很简单 - 有一个静态变量,ctors增量,dtors减少) ).尝试做它作为一个练习!

另一个有用的例子,对于提升(我不确定他们是如何实现它的,但是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 <
}
Run Code Online (Sandbox Code Playgroud)

现在你可以像这样使用它

struct Apple:public Equality<Apple> 
{
    int size;
};

bool operator < (Apple const & a1, Apple const& a2)
{
    return a1.size < a2.size;
}
Run Code Online (Sandbox Code Playgroud)

现在,你没有为apple明确提供operator ==?但你拥有它!你可以写

int main()
{
    Apple a1;
    Apple a2; 

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

如果你只是为Apple编写operator ==,你可能会写得更少,但想象Equality模板不仅会提供==而且>,> =,<=等等.你可以将这些定义用于多个类,重用代码!

CRTP是一件很棒的事情:HTH

  • 这篇文章并没有提倡将单例作为一种好的编程模式.它只是将它作为一个可以被普遍理解的例证.在这里,1是无根据的 (59认同)
  • @Armen:答案解释CRTP的方式可以清楚地理解,这是一个很好的答案,谢谢你这么好的答案. (3认同)
  • @Puppy:辛格尔顿并不可怕.当其他方法更合适时,它被低于平均水平的程序员过度使用,但是它的大多数用法都很糟糕,并没有使模式本身变得糟糕.有些情况下,单身人士是最好的选择,尽管这种情况很少见. (2认同)

Gut*_*Mac 44

在这里你可以看到一个很好的例子.如果使用虚方法,程序将知道在运行时执行什么.实现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);
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 使用纯虚方法,您将在运行时而不是编译时解决继承问题.CRTP用于在编译时解决这个问题,因此执行速度会更快. (20认同)
  • 尝试创建一个需要抽象 Writer 的普通函数:你不能这样做,因为在任何地方都没有名为 Writer 的类,那么你的多态究竟在哪里?这根本不等同于虚函数,而且它的用处要小得多。 (2认同)

blu*_*kin 20

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

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

derived foo
AnotherDerived foo
Run Code Online (Sandbox Code Playgroud)

  • 我不明白这段代码的意义,因为使用“void ProcessFoo(T* b)”并且没有 Derived 和 AnotherDerived 实际上派生它仍然可以工作。恕我直言,如果 ProcessFoo 不以某种方式使用模板会更有趣。 (4认同)
  • 这是我最喜欢的答案,因为它也说明了为什么这个模式对`ProcessFoo()`函数很有用. (3认同)
  • @GabrielDevillers 首先,模板化的“ProcessFoo()”将与实现该接口的任何类型一起使用,即在这种情况下,输入类型 T 应该有一个名为“foo()”的方法。其次,为了让非模板化的“ProcessFoo”能够处理多种类型,您可能最终会使用 RTTI,这是我们想要避免的。此外,模板化版本还为您提供了界面上的编译时间检查。 (3认同)
  • 在此示例中添加一个示例,说明如何在 Base 类中实现默认 foo() ,如果没有 Derived 实现它,则会调用该示例。AKA 将 Base 中的 foo 更改为其他名称(例如 caller()),将新函数 foo() 添加到 cout 的“Base”的 Base 中。然后在 ProcessFoo 内部调用 caller() (2认同)

Jic*_*hao 5

同样注意:

CRTP可用于实现静态多态性(类似于动态多态,但没有虚函数指针表).

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出将是:

Derived1 method
Derived2 method
Run Code Online (Sandbox Code Playgroud)

  • 不好的例子.这段代码可以在没有使用CRTP的情况下完成.vtable真正提供的是使用基类(指针或引用)来调用派生方法.您应该在这里展示如何使用CRTP完成它. (29认同)
  • 在您的示例中,甚至不调用`Base <> :: method()`,也不在任何地方使用多态. (16认同)

Már*_*ldi 5

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


的一个很好的具体的例子CRTPstd::enable_shared_from_this从C ++ 11:

[util.smartptr.enab] / 1

T可以继承自来enable_­shared_­from_­this<T>继承shared_­from_­this获得shared_­ptr指向的实例的成员函数*this

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

当您需要给一个,std::shared_ptr但您只能访问时,这很有用*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));
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)