एक पुस्तक का संदर्भ लिए बिना, क्या कोई कृपया CRTP
एक कोड उदाहरण के साथ एक अच्छी व्याख्या प्रदान कर सकता है ?
एक पुस्तक का संदर्भ लिए बिना, क्या कोई कृपया CRTP
एक कोड उदाहरण के साथ एक अच्छी व्याख्या प्रदान कर सकता है ?
जवाबों:
संक्षेप में, 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
यहां आप एक महान उदाहरण देख सकते हैं। यदि आप वर्चुअल विधि का उपयोग करते हैं, तो प्रोग्राम को पता चल जाएगा कि रनटाइम में क्या निष्पादित होता है। 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
अन्य काम करते समय सुपर सहायक लगती है।
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
foo()
जो व्युत्पन्न वर्ग द्वारा लागू किया जाता है।
ProcessFoo()
फ़ंक्शन के साथ क्यों उपयोगी है ।
void ProcessFoo(T* b)
डेरेव्ड एंड अदरड्रेव्ड के बिना और वास्तव में व्युत्पन्न होने के बाद भी यह काम करेगा। IMHO यह अधिक दिलचस्प होगा अगर ProcessFoo ने किसी भी तरह से टेम्पलेट्स का उपयोग नहीं किया।
ProcessFoo()
किसी भी प्रकार के साथ काम करेगा जो इंटरफ़ेस को लागू करता है अर्थात इस मामले में इनपुट प्रकार T में एक विधि होनी चाहिए foo()
। दूसरा, ProcessFoo
कई प्रकारों के साथ काम करने के लिए गैर-अस्थायी रूप से प्राप्त करने के लिए , आप संभवतः आरटीटीआई का उपयोग करके समाप्त हो जाएंगे जो कि हम बचना चाहते हैं। इसके अलावा, templatized संस्करण आपको इंटरफ़ेस पर संकलन समय की जांच प्रदान करता है।
यह एक सीधा जवाब नहीं है, बल्कि एक उदाहरण है कि CRTP कैसे उपयोगी हो सकता है।
CRTP का एक अच्छा ठोस उदाहरण std::enable_shared_from_this
C ++ 11 से है:
एक वर्ग
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);
नोट के रूप में:
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
vtable
बिना CRTP का उपयोग किए बिना एस के साथ किया जा सकता है । vtable
व्युत्पन्न विधियों को कॉल करने के लिए बेस क्लास (पॉइंटर या संदर्भ) का सही उपयोग क्या प्रदान करता है। आपको यह दिखाना चाहिए कि यह CRTP के साथ कैसे किया जाता है।
Base<>::method ()
यह भी नहीं कहा जाता है, और न ही आप कहीं भी बहुरूपता का उपयोग नहीं करते हैं।
methodImpl
में method
की Base
और व्युत्पन्न वर्ग में नाम methodImpl
के बजायmethod