В C ++ класс наследует от класса шаблона, который генерируется сам по себе [duplicate]

В bash вы можете использовать $ {! variable} для использования переменных переменных.

foo="something"
bar="foo"
echo "${!bar}"
144
задан TankorSmash 4 January 2017 в 23:55
поделиться

5 ответов

Короче говоря, CRTP - это когда класс A имеет базовый класс, который является специализированным шаблоном для самого класса A. Например,

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

Он является любопытно повторяющимся, не так ли? :)

Теперь, что это дает вам? Это фактически дает 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;

Теперь, чтобы сделать произвольный класс A синглом, вы должны это сделать

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

Итак, вы видите? Шаблон singleton предполагает, что его специализация для любого типа X будет унаследована от singleton<X> и, следовательно, будет доступна все его (общедоступные, защищенные) члены, включая GetInstance! Существуют и другие полезные применения CRTP. Например, если вы хотите подсчитать все экземпляры, которые в настоящее время существуют для вашего класса, но хотите инкапсулировать эту логику в отдельный шаблон (идея для конкретного класса довольно проста - имеет статическую переменную, прирост в ctors, декремент в dtors ). Постарайтесь сделать это как упражнение!

Еще один полезный пример для boost (я не уверен, как они его реализовали, но CRTP тоже будет делать). Представьте, что вы хотите предоставить только оператор & lt; для ваших классов, но автоматически operator == для них!

вы могли бы сделать это следующим образом:

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

Теперь вы можете использовать его следующим образом:

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 предоставил бы не только ==, но>,> =, & lt ; = и т. д. И вы можете использовать эти определения для классов multiple , повторно используя код!

CRTP - замечательная вещь :) HTH

222
ответ дан ibrahim5253 16 August 2018 в 05:42
поделиться
  • 1
    @DeadMG: Честно говоря, этот ответ был полезен в понимании концепции CRTP, хотя синглтон не был бы лучшим выбором примера, но ответ был полезен цели. Поскольку вы проигнорировали, я надеюсь, что вы придумаете лучший ответ / пример & amp ; оставляя окно открытым для вас, не отмечая это как принятый ответ. – Alok Save 13 November 2010 в 17:41
  • 2
    + 1 лучшее объяснение, которое я когда-либо видел – John Dibling 13 November 2010 в 18:20
  • 3
    Этот пост не защищает синглтон как хороший программный шаблон. Он просто использует его как иллюстрацию, которая может быть понята. Имо-1 является необоснованным – John Dibling 13 November 2010 в 18:26
  • 4
    @John: Ты изменил ваш мобильный телефон, потому что слишком сложно использовать SO из него? :) – sbi 14 November 2010 в 22:06
  • 5
    @Armen: Ответ объясняет CRTP таким образом, который можно понять четко, его хороший ответ, спасибо за такой хороший ответ. – Alok Save 16 November 2010 в 16:02

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
4
ответ дан blueskin 16 August 2018 в 05:42
поделиться
  • 1
    В этом примере также может быть полезно добавить пример реализации foo () по умолчанию в базовом классе, который будет вызываться, если Derived не выполнил его. AKA смените foo на Base на другое имя (например, caller ()), добавьте новую функцию foo () в базу, где cout's «Base». Затем вызовите caller () внутри ProcessFoo – wizurd 11 April 2018 в 15:15
  • 2
    @wizurd Этот пример больше иллюстрирует функцию чистого виртуального базового класса, то есть мы обеспечиваем выполнение foo() производным классом. – blueskin 4 June 2018 в 17:01
  • 3

Здесь вы можете увидеть отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Внедряя 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);
    }
};
34
ответ дан Edgar Rokjān 16 August 2018 в 05:42
поделиться
  • 1
    Не могли бы вы это сделать, указав virtual void write(const char* str) const = 0;? Хотя это справедливо, этот метод кажется очень полезным, когда write выполняет другую работу. – atlex2 9 August 2016 в 14:44
  • 2
    Используя чистый виртуальный метод, вы решаете наследование во время выполнения вместо времени компиляции. CRTP используется для решения этой проблемы во время компиляции, поэтому выполнение будет быстрее. – GutiMac 10 August 2016 в 07:31

Также как примечание:

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
6
ответ дан Jichao 16 August 2018 в 05:42
поделиться
  • 1
    извините, мой плохой, static_cast позаботится об изменении. Если вы хотите увидеть угловой случай в любом случае, хотя это не вызывает ошибки, см. Здесь: ideone.com/LPkktf – odinthenerd 6 December 2013 в 11:01
  • 2
    Плохой пример. Этот код может быть выполнен без использования vtable s без использования CRTP. То, что vtable действительно обеспечивает, использует базовый класс (указатель или ссылку) для вызова производных методов. Вы должны показать, как это делается с помощью CRTP. – Etherealone 6 February 2014 в 19:37
  • 3
    В вашем примере Base<>::method () даже не вызывается, и вы нигде не используете полиморфизм. – MikeMB 12 March 2015 в 10:14
  • 4
    @Jichao, согласно примечанию @MikeMB, вы должны называть methodImpl в method из Base и в производных классах имя methodImpl вместо method – Ivan Kush 17 September 2016 в 17:20
  • 5
    если вы используете аналогичный метод (), то его статически привязаны, и вам не нужен общий базовый класс. Потому что в любом случае вы не можете использовать его полиморфно через указатель базового класса или ref. Поэтому код должен выглядеть так: #include & lt; iostream & gt; template & lt; typename T & gt; struct Writer {void write () {static_cast & lt; T * & gt; (this) - & gt; writeImpl (); }}; struct Derived1: public Writer & lt; Derived1 & gt; {void writeImpl () {std :: cout & lt; & Quot; D1 & Quot ;; }}; struct Derived2: public Writer & lt; Derived2 & gt; {void writeImpl () {std :: cout & lt; & Quot; DER2 & Quot ;; }}; – barney 2 June 2017 в 20:02

Это не прямой ответ, а пример того, как CRTP может быть полезен.


Хороший конкретный пример CRTP является std::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));
    }
};

Причина, по которой вы не можете просто передать 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);
]
3
ответ дан Mário Feroldi 16 August 2018 в 05:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: