जिज्ञासु आवर्ती टेम्पलेट पैटर्न (CRTP) क्या है?


187

एक पुस्तक का संदर्भ लिए बिना, क्या कोई कृपया CRTPएक कोड उदाहरण के साथ एक अच्छी व्याख्या प्रदान कर सकता है ?


2
SO पर CRTP प्रश्न पढ़ें: stackoverflow.com/questions/tagged/crtp । वह आपको कुछ विचार दे सकता है।
sbi

68
@ एसबीआई: यदि वह ऐसा करता है, तो वह अपना प्रश्न खोजेगा। और वह उत्सुकता से आवर्ती होगी। :)
क्रेग मैकक्वीन

1
BTW, यह मुझे लगता है कि शब्द "उत्सुकता से पीछे हटना" होना चाहिए। क्या मैं अर्थ को गलत समझ रहा हूं?
क्रेग मैकक्वीन

1
क्रेग: मुझे लगता है कि आप हैं; यह "उत्सुकता से आवर्ती" इस अर्थ में है कि यह कई संदर्भों में फसल के लिए पाया गया था।
गैरेथ मैककॉघन

जवाबों:


275

संक्षेप में, 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
};

तो आप देखते हैं? सिंगलटन टेम्पलेट मानता है कि किसी भी प्रकार के लिए इसकी विशेषज्ञता Xविरासत में मिली singleton<X>होगी और इस तरह इसके सभी (सार्वजनिक, संरक्षित) सदस्य सुलभ होंगे, सहित GetInstance! 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 <
}

अब आप इसे इस तरह से उपयोग कर सकते हैं

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


61
यह पोस्ट एक अच्छे प्रोग्रामिंग पैटर्न के रूप में सिंगलटन की वकालत नहीं करता है। यह बस एक दृष्टांत के रूप में इसका उपयोग करता है जिसे आमतौर पर समझा जा सकता है।
जिमो

3
@Armen: जवाब एक तरह से CRTP की व्याख्या करता है जिसे स्पष्ट रूप से समझा जा सकता है, यह एक अच्छा जवाब है, इस तरह के एक अच्छे उत्तर के लिए धन्यवाद।
आलोक सेव

1
@Armen: इस महान स्पष्टीकरण के लिए धन्यवाद। मैं पहले CRTP प्राप्त नहीं कर रहा था, लेकिन समानता का उदाहरण रोशन कर रहा है! +1
पॉल

1
फिर भी CRTP का उपयोग करने का एक और उदाहरण है जब आपको एक गैर-प्रतिलिपि योग्य वर्ग की आवश्यकता होती है: टेम्पलेट <वर्ग T> वर्ग NonCopyable {संरक्षित: NonCopyable () {} ~ NonCopyable () {} निजी: NonCopyable (Const NonCopyable &); नॉनकैपेबल और ऑपरेटर = (कांस्टेबल नॉनकैपेबल और); }; फिर आप नीचे दिए गए गैर-उपयोग योग्य का उपयोग करें: वर्ग म्यूटेक्स: निजी नॉनकॉपीबल <म्यूटेक्स> {पब्लिक: शून्य लॉक () {} शून्य अनलॉक () {}};
वीरेन

2
@Puppy: सिंगलटन भयानक नहीं है। जब तक अन्य दृष्टिकोण अधिक उपयुक्त होंगे, तब तक यह औसत प्रोग्रामर द्वारा नीचे से अधिक उपयोग किया जाता है, लेकिन इसके अधिकांश उपयोग भयानक होते हैं, यह पैटर्न को ही भयानक नहीं बनाता है। ऐसे मामले हैं जहां सिंगलटन सबसे अच्छा विकल्प है, हालांकि वे दुर्लभ हैं।
कैसरलुडी

47

यहां आप एक महान उदाहरण देख सकते हैं। यदि आप वर्चुअल विधि का उपयोग करते हैं, तो प्रोग्राम को पता चल जाएगा कि रनटाइम में क्या निष्पादित होता है। 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);
    }
};

क्या आप इसे परिभाषित नहीं कर सकते हैं virtual void write(const char* str) const = 0;? हालांकि निष्पक्ष होने के लिए, यह तकनीक writeअन्य काम करते समय सुपर सहायक लगती है।
atlex2

26
एक शुद्ध आभासी विधि का उपयोग करके आप संकलन समय के बजाय रनटाइम में विरासत को हल कर रहे हैं। CRTP का उपयोग इसे संकलन समय में हल करने के लिए किया जाता है ताकि निष्पादन तेजी से हो।
GutiMac

1
एक सादे फंक्शन बनाने की कोशिश करें जो एक अमूर्त लेखक की अपेक्षा करता है: आप ऐसा नहीं कर सकते क्योंकि कहीं भी राइटर नाम का कोई वर्ग नहीं है, तो आपका पॉलीमॉर्फिज़्म कहाँ है? यह वर्चुअल फ़ंक्शंस के बराबर नहीं है और यह बहुत कम उपयोगी है।

22

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

1
यह इस उदाहरण में भी हो सकता है कि बेस क्लास में डिफ़ॉल्ट फ़ू () को लागू करने के तरीके को जोड़ने के लिए एक उदाहरण जोड़ा जाए जिसे कोई व्युत्पन्न नहीं कहा गया है। AKA बेस में foo को किसी अन्य नाम (जैसे कॉलर) () में बदलें, एक नया फंक्शन foo () को उस बेस के "बेस" में जोड़ें। इसके बाद ProcessFoo के अंदर कॉलर () को कॉल करें
wizurd

@wizurd यह उदाहरण एक शुद्ध वर्चुअल बेस क्लास फ़ंक्शन को चित्रित करने के लिए अधिक है यानी हम इसे लागू करते हैं foo()जो व्युत्पन्न वर्ग द्वारा लागू किया जाता है।
ब्लूस्किन

3
यह मेरा पसंदीदा उत्तर है, क्योंकि यह भी दिखाता है कि यह पैटर्न ProcessFoo()फ़ंक्शन के साथ क्यों उपयोगी है ।
पिट्रो

मुझे इस कोड की बात नहीं आती, क्योंकि void ProcessFoo(T* b)डेरेव्ड एंड अदरड्रेव्ड के बिना और वास्तव में व्युत्पन्न होने के बाद भी यह काम करेगा। IMHO यह अधिक दिलचस्प होगा अगर ProcessFoo ने किसी भी तरह से टेम्पलेट्स का उपयोग नहीं किया।
गेब्रियल डेविलर्स

1
@GabrielDevillers सबसे पहले, templatized ProcessFoo()किसी भी प्रकार के साथ काम करेगा जो इंटरफ़ेस को लागू करता है अर्थात इस मामले में इनपुट प्रकार T में एक विधि होनी चाहिए foo()। दूसरा, ProcessFooकई प्रकारों के साथ काम करने के लिए गैर-अस्थायी रूप से प्राप्त करने के लिए , आप संभवतः आरटीटीआई का उपयोग करके समाप्त हो जाएंगे जो कि हम बचना चाहते हैं। इसके अलावा, templatized संस्करण आपको इंटरफ़ेस पर संकलन समय की जांच प्रदान करता है।
ब्लूसकिन

6

यह एक सीधा जवाब नहीं है, बल्कि एक उदाहरण है कि CRTP कैसे उपयोगी हो सकता है।


CRTP का एक अच्छा ठोस उदाहरण std::enable_shared_from_thisC ++ 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);

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

उत्पादन होगा:

Derived1 method
Derived2 method

1
क्षमा करें मेरे बुरे, static_cast परिवर्तन का ख्याल रखता है। यदि आप वैसे भी कोने के मामले को देखना चाहते हैं, तो यह त्रुटि का कारण नहीं बनता है यहां देखें: ideone.com/LPkktf
odinthenerd

30
खराब उदाहरण। इस कोड को vtableबिना CRTP का उपयोग किए बिना एस के साथ किया जा सकता है । vtableव्युत्पन्न विधियों को कॉल करने के लिए बेस क्लास (पॉइंटर या संदर्भ) का सही उपयोग क्या प्रदान करता है। आपको यह दिखाना चाहिए कि यह CRTP के साथ कैसे किया जाता है।
ईथरलोन

17
आपके उदाहरण में, Base<>::method ()यह भी नहीं कहा जाता है, और न ही आप कहीं भी बहुरूपता का उपयोग नहीं करते हैं।
माइकएम

1
@Jichao, @MikeMB की टिप्पणी के अनुसार, आप बुलाना चाहिए methodImplमें methodकी Baseऔर व्युत्पन्न वर्ग में नाम methodImplके बजायmethod
इवान कुश

1
यदि आप इसी तरह की विधि () का उपयोग करते हैं, तो इसके स्टैटिकली बाउंड और आपको कॉमन बेस क्लास की आवश्यकता नहीं है। क्योंकि वैसे भी आप इसे बेस क्लास पॉइंटर या रेफ के माध्यम से पॉलीमॉर्फिक रूप से उपयोग नहीं कर सकते। तो कोड इस तरह दिखना चाहिए: #include <iostream> टेम्पलेट <टाइपनेम टी> संरचना लेखक {शून्य लिखना () {static_cast <T *> (यह) -> writeImpl (); }}; संरचना Derived1: सार्वजनिक लेखक <Derived1> {void writeImpl () {std :: cout << "1-1 "; }}; संरचना Derived2: सार्वजनिक लेखक <Derived2> {void writeImpl () {std :: cout << "DER2"; }};
बार्नी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.