在不参考书籍的情况下,谁能用一个代码示例很好地解释CRTP ?


当前回答

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

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() {}
};

这里,我们应该关注的主要参数是第一个参数T*,它将是一个状态被改变的对象。第二个参数 将是被改变的字段,它可以是任何东西,甚至你可以省略它,这不是我们的主题的问题(在这种情况下,它是std::的变体 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>: class date: public observable<date>派生出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);
    }
};

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

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

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.

其他回答

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

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() {}
};

这里,我们应该关注的主要参数是第一个参数T*,它将是一个状态被改变的对象。第二个参数 将是被改变的字段,它可以是任何东西,甚至你可以省略它,这不是我们的主题的问题(在这种情况下,它是std::的变体 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>: class date: public observable<date>派生出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);
    }
};

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

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

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.

CRTP是一种实现编译时多态性的技术。这里有一个非常简单的例子。在下面的示例中,ProcessFoo()使用基类接口,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如何发挥作用的一个例子。


一个很好的CRTP的具体例子是std::enable_shared_from_this from c++ 11:

(util.smartptr.enab) / 1 类T可以从enable_-shared_-from_-this <T>继承shared_-from_-this成员函数,该成员函数获取指向*this的shared_-ptr实例。

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

你不能直接传递这个而不是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);

这里你可以看到一个很好的例子。如果你使用虚方法,程序将知道在运行时执行什么。实现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可用于实现静态多态性(与动态多态性类似,但没有虚函数指针表)。

#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