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

发布于 2024-10-01 18:18:39 字数 66 浏览 1 评论 0原文

在不参考书籍的情况下,任何人都可以通过代码示例为CRTP(奇怪的重复模板模式)提供一个很好的解释吗?

Without referring to a book, can anyone please provide a good explanation for CRTP (curiously recurring template pattern) with a code example?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

各自安好 2024-10-08 18:18:39

简而言之,CRTP 是指类 A 具有一个基类,该基类是类 A 本身的模板特化。例如,

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

奇怪地重复出现,不是吗? :)

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

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

#include <iostream>

template <class T>
class Singleton
{
public:
     static T* GetInstance() {
         if ( p == nullptr ) p = new T();
         return p;
     }
protected:
     Singleton() = default;

     Singleton(Singleton const &) = delete;
     Singleton &operator=(const Singleton &) = delete;

private:
     static T *p;
};
template <class T>
T *Singleton<T>::p= nullptr;

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

class A : public Singleton<A> 
{ 
friend Singleton;
private:
    A() = default;
};
A *a0= A::GetInstance();

但是,在这种情况下不需要 CRTP,请参阅如下:

class C 
{ 
friend Singleton<C>; 
private: C() = default;
};
C *c1= Singleton<C>::GetInstance();

看到了吗?单例模板假定其对任何类型 X 的专门化都将从 singleton 继承,因此将使其所有(公共、受保护)成员可访问,包括 <代码>获取实例! CRTP 还有其他有用的用途。例如,如果您想要计算类当前存在的所有实例,但想要将此逻辑封装在单独的模板中(具体类的想法非常简单 - 有一个静态变量,以 ctor 为单位递增,以 dtor 为单位递减) )。尝试将其作为练习!

还有一个有用的例子,对于 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 <
}

或者在模板范围内实现而不进行强制转换

template<class T>
class Equality
{
    friend bool operator == (const T& op1, const T& op2)
    { 
        return !(op1 < op2) && !(op2 < op1); 
    }
};

现在你可以像这样使用它

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

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

#include <iostream>

template <class T>
class Singleton
{
public:
     static T* GetInstance() {
         if ( p == nullptr ) p = new T();
         return p;
     }
protected:
     Singleton() = default;

     Singleton(Singleton const &) = delete;
     Singleton &operator=(const Singleton &) = delete;

private:
     static T *p;
};
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> 
{ 
friend Singleton;
private:
    A() = default;
};
A *a0= A::GetInstance();

Howevry, CRTP is not necessary in this case, see as follow:

class C 
{ 
friend Singleton<C>; 
private: C() = default;
};
C *c1= Singleton<C>::GetInstance();

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

or implement within the template scope without casting

template<class T>
class Equality
{
    friend bool operator == (const T& op1, const T& op2)
    { 
        return !(op1 < op2) && !(op2 < op1); 
    }
};

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

假扮的天使 2024-10-08 18:18:39

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

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);
    }
};
茶底世界 2024-10-08 18:18:39

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

输出:

derived foo
AnotherDerived foo

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
罪歌 2024-10-08 18:18:39

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


CRTP 的一个很好的具体示例是 C++11 中的 std::enable_shared_from_this

[util.smartptr.enab]/1

T可以继承enable_shared_from_this来继承获取shared_ptrshared_from_this成员函数> 指向 *this 的实例。

也就是说,从 std::enable_shared_from_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));
    }
};

您不能只传递 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);

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);
提赋 2024-10-08 18:18:39

注意:

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

输出将是:

Derived1 method
Derived2 method

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
束缚m 2024-10-08 18:18:39

使用 CRTP 的另一个很好的例子是观察者设计模式的实现。一个小例子可以这样构建。

假设您有一个类 date 和一些侦听器类,例如 date_drawerdate_reminder 等。侦听器类(观察者)
每当日期发生变化时,主题类date(可观察的)应该通知他们,以便他们可以完成他们的工作(在某些情况下绘制日期)
格式、提醒特定日期等)。您可以做的是拥有两个应该派生的参数化基类 observerobservable
您的 date 和观察者类(在我们的例子中为 date_drawer)。观察者设计模式的实现可以参考GOF等经典书籍。这里我们只需要
重点介绍 CRTP 的使用。我们来看一下。
在我们的草案实现中,observer 基类有一个纯虚拟方法,每当状态发生变化时,observable 类就应该调用该方法,
我们将此方法称为state_changed。我们来看看这个小抽象基类的代码。

template <typename T>
struct observer
{
    virtual void state_changed(T*, variant<string, int, bool>) = 0;
    virtual ~observer() {}
};

在这里,我们应该关注的主要参数是第一个参数 T*,它将是状态更改的对象。第二个参数
将是被更改的字段,它可以是任何内容,甚至可以省略它,这不是我们主题的问题(在本例中,它是 std::variant
3 个字段)。
第二个基类

template <typename T>
class observable
{
    vector<unique_ptr<observer<T>>> observers;
protected:
    void notify_observers(T* changed_obj, variant<string, int, bool> changed_state)
    {
        for (unique_ptr<observer<T>>& o : observers)
        {
            o->state_changed(changed_obj, changed_state);
        }
    }
public:
    void subscribe_observer(unique_ptr<observer<T>> o)
    {
        observers.push_back(move(o));
    }
    void unsubscribe_observer(unique_ptr<observer<T>> o)
    {

    }
};

也是一个参数类,它依赖于类型 T* ,并且与传递给内部的 state_changed 函数的对象相同。
notify_observers 函数。
剩下的只是介绍实际的主题类date和观察者类date_drawer这里使用了 CRTP 模式,我们从 observable 派生出 date observable 类: class date : public observable.

class date : public observable<date>
{
    string date_;
    int code;
    bool is_bank_holiday;

public:
    void set_date_properties(int code_ = 0, bool is_bank_holiday_ = false)
    {
        code = code_;
        is_bank_holiday = is_bank_holiday_;
        //...
        notify_observers(this, code);
        notify_observers(this, is_bank_holiday);
    }

    void set_date(const string& new_date, int code_ = 0, bool is_bank_holiday_ = false) 
    { 
        date_ = new_date; 
        //...
        notify_observers(this, new_date);
    }
    string get_date() const { return date_; }
};

class date_drawer : public observer<date>
{
public:
    void state_changed(date* c, variant<string, int, bool> state) override
    {
        visit([c](const auto& x) {cout << "date_drawer notified, new state is " << x << ", new date is " << c->get_date() << endl; }, state);
    }
};

让我们编写一些客户端代码:

date c;
c.subscribe_observer(make_unique<date_drawer>());
c.set_date("27.01.2022");
c.set_date_properties(7, true);

该测试程序的输出将是。

date_drawer notified, new state is 27.01.2022, new date is 27.01.2022
date_drawer notified, new state is 7, new date is 27.01.2022
date_drawer notified, new state is 1, new date is 27.01.2022

请注意,每当发生状态更改时,使用 CRTP 并将 this 传递给通知 notify_observers 函数(此处为 set_date_propertiesset_date )。允许我们在实际 date_drawer< 中重写 void state_changed(date* c,variantstate) 纯虚函数时使用 date* /code> 观察者类,因此我们里面有 date* c (不是 observable*),例如我们可以调用 date* 的非虚函数 (在我们的例子中为 get_date
state_changed 函数内。
我们可以避免使用 CRTP,因此不参数化观察者设计模式实现,并在任何地方使用可观察的基类指针。这样我们可以达到相同的效果,但在这种情况下,每当我们想要使用派生类指针时(即使不是很推荐),我们都应该使用 dynamic_cast 向下转型,这会产生一些运行时开销。

Another good example of using CRTP can be an implementation of observer design pattern. A small example can be constructed like this.

Suppose you have a class date and you have some listener classes like date_drawer, date_reminder, etc.. The listener classes (observers)
should be notified by the subject class date (observable) whenever a date change is done so that they can do their job (draw a date in some
format, remind for a specific date, etc.). What you can do is to have two parametrized base classes observer and observable from which you should derive
your date and observer classes (date_drawer in our case). For the observer design pattern implementation refer to the classic books like GOF. Here we only need to
highlight the use of CRTP. Let's look at it.
In our draft implementation observer base class has one pure virtual method which should be called by the observable class whenever a state change occurred,
let's call this method state_changed. Let's look at the code of this small abstract base class.

template <typename T>
struct observer
{
    virtual void state_changed(T*, variant<string, int, bool>) = 0;
    virtual ~observer() {}
};

Here, the main parameter that we should focus on is the first argument T*, which is going to be the object for which a state was changed. The second parameter
is going to be the field that was changed, it can be anything, even you can omit it, that's not the problem of our topic (in this case it's a std::variant of
3 fields).
The second base class is

template <typename T>
class observable
{
    vector<unique_ptr<observer<T>>> observers;
protected:
    void notify_observers(T* changed_obj, variant<string, int, bool> changed_state)
    {
        for (unique_ptr<observer<T>>& o : observers)
        {
            o->state_changed(changed_obj, changed_state);
        }
    }
public:
    void subscribe_observer(unique_ptr<observer<T>> o)
    {
        observers.push_back(move(o));
    }
    void unsubscribe_observer(unique_ptr<observer<T>> o)
    {

    }
};

which is also a parametric class that depends on the type T* and that's the same object that is passed to the state_changed function inside the
notify_observers function.
Remains only to introduce the actual subject class date and observer class date_drawer. Here the CRTP pattern is used, we derive the date observable class from observable<date>: class date : public observable<date>.

class date : public observable<date>
{
    string date_;
    int code;
    bool is_bank_holiday;

public:
    void set_date_properties(int code_ = 0, bool is_bank_holiday_ = false)
    {
        code = code_;
        is_bank_holiday = is_bank_holiday_;
        //...
        notify_observers(this, code);
        notify_observers(this, is_bank_holiday);
    }

    void set_date(const string& new_date, int code_ = 0, bool is_bank_holiday_ = false) 
    { 
        date_ = new_date; 
        //...
        notify_observers(this, new_date);
    }
    string get_date() const { return date_; }
};

class date_drawer : public observer<date>
{
public:
    void state_changed(date* c, variant<string, int, bool> state) override
    {
        visit([c](const auto& x) {cout << "date_drawer notified, new state is " << x << ", new date is " << c->get_date() << endl; }, state);
    }
};

Let's write some client code:

date c;
c.subscribe_observer(make_unique<date_drawer>());
c.set_date("27.01.2022");
c.set_date_properties(7, true);

the output of this test program will be.

date_drawer notified, new state is 27.01.2022, new date is 27.01.2022
date_drawer notified, new state is 7, new date is 27.01.2022
date_drawer notified, new state is 1, new date is 27.01.2022

Note that using CRTP and passing this to the notify notify_observers function whenever a state change occurred (set_date_properties and set_date here). Allowed us to use date* when overriding void state_changed(date* c, variant<string, int, bool> state) pure virtual function in the actual date_drawer observer class, hence we have date* c inside it (not observable*) and for example we can call a non-virtual function of date* (get_date in our case)
inside the state_changed function.
We could of refrain from wanting to use CRTP and hence not parametrizing the observer design pattern implementation and use observable base class pointer everywhere. This way we could have the same effect, but in this case whenever we want to use the derived class pointer (even though not very recomendeed) we should use dynamic_cast downcasting which has some runtime overhead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文