কৌতূহলীভাবে পুনরাবৃত্তি টেম্পলেট প্যাটার্ন (সিআরটিপি) কী?


187

কোনও বইয়ের উল্লেখ না করে, কেউ দয়া করে CRTPকোনও কোড উদাহরণ সহ একটি ভাল ব্যাখ্যা সরবরাহ করতে পারেন ?


2
সিআরটিপি প্রশ্নগুলি এসওতে পড়ুন: stackoverflow.com/questions/tagged/crtp । এটি আপনাকে কিছু ধারণা দিতে পারে।
এসবিআই

68
@ এসবিআই: সে যদি তা করে তবে সে তার নিজের প্রশ্ন খুঁজে পাবে। এবং এটি কৌতূহলীভাবে পুনরাবৃত্তি হবে। :)
ক্রেগ ম্যাককুইন

1
বিটিডাব্লু, মনে হয় এই শব্দটি "কৌতূহলীভাবে পুনরাবৃত্তি" হওয়া উচিত। আমি কি অর্থ বোঝাচ্ছি?
ক্রেগ ম্যাককুইন

1
ক্রেগ: আমি মনে করি আপনি; এটি "কৌতূহলীভাবে পুনরাবৃত্তি" এই অর্থে যে এটি একাধিক প্রসঙ্গে ছড়িয়ে পড়েছে।
গ্যারেথ ম্যাককোগান

উত্তর:


275

সংক্ষেপে, সিআরটিপি হয় যখন কোনও ক্লাসের 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! সিআরটিপি এর অন্যান্য দরকারী ব্যবহার রয়েছে। উদাহরণস্বরূপ, আপনি যদি বর্তমানে আপনার শ্রেণীর জন্য বিদ্যমান সমস্ত উদাহরণ গণনা করতে চান তবে এই যুক্তিটিকে একটি পৃথক টেম্পলেটে আবদ্ধ করতে চান (একটি কংক্রিট বর্গের ধারণাটি বেশ সহজ - একটি স্ট্যাটিক ভেরিয়েবল, সিটারে বর্ধন, ডিটারে হ্রাস) )। অনুশীলন হিসাবে এটি করার চেষ্টা করুন!

তবুও আরেকটি দরকারী উদাহরণ, বুস্টের জন্য (তারা কীভাবে এটি প্রয়োগ করেছে তা আমি নিশ্চিত নই, তবে সিআরটিপিও তা করবে) do কল্পনা করুন আপনি <নিজের ক্লাসের জন্য কেবল অপারেটর সরবরাহ করতে চান তবে ==তাদের জন্য স্বয়ংক্রিয়ভাবে অপারেটর !

আপনি এটি এইভাবে করতে পারে:

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টেমপ্লেট না শুধুমাত্র প্রদান করবে ==কিন্তু >, >=, <=ইত্যাদি আপনার জন্য এই সংজ্ঞা ব্যবহার করতে পারে একাধিক ক্লাস, কোড পুনঃব্যবহার!

সিআরটিপি একটি দুর্দান্ত জিনিস :) এইচটিএইচ


61
এই পোস্টটি সিঙ্গেলটনকে একটি ভাল প্রোগ্রামিং প্যাটার্ন হিসাবে সমর্থন করে না it এটি সাধারণভাবে বোঝা যায় এমন চিত্র হিসাবে এটি ব্যবহার করে im -1 অযৌক্তিক নয়
জন ডিবলিং

3
@ আর্মেন: উত্তরটি সিআরটিপিকে এমনভাবে ব্যাখ্যা করে যা পরিষ্কারভাবে বোঝা যায়, এটি একটি দুর্দান্ত উত্তর, এত সুন্দর উত্তরের জন্য ধন্যবাদ।
অলোক সেভ করুন

1
@ আর্মেন: এই দুর্দান্ত ব্যাখ্যার জন্য ধন্যবাদ। আমি আগে সিআরটিপি পাচ্ছিলাম না, তবে সাম্যের উদাহরণটি আলোকিত করে চলেছে! +1
পল

1
তবুও সিআরটিপি ব্যবহারের আরেকটি উদাহরণ হ'ল আপনার যখন অন-অনুলিপিযোগ্য শ্রেণি দরকার: টেমপ্লেট <ক্লাস টি> শ্রেণি ননকপিযোগ্য {সুরক্ষিত: ননকপিযোগ্য () {} ~ ননকপিযোগ্য () {} ব্যক্তিগত: ননকপিযোগ্য (কনট ননকপিযোগ্য এবং); ননকপিযোগ্য এবং অপারেটর = (কনস্ট্রিভ ননকপিযোগ্য &); }; তারপরে আপনি নীচের মতো ননকপিযোগ্য ব্যবহার করুন: শ্রেণি নিঃশব্দ: প্রাইভেট ননকপিযোগ্য <মিটেক্স> {সর্বজনীন: শূন্য লক () oid} শূন্য আনলক () {}};
বীরেন

2
@ পপি: সিঙ্গলটন ভয়াবহ নয়। এটি औसत প্রোগ্রামারদের নীচে দ্বারা খুব বেশি ব্যবহার করা হয় যখন অন্যান্য পদ্ধতিগুলি আরও উপযুক্ত হবে তবে এটির বেশিরভাগ ব্যবহার ভয়ানক হ'ল প্যাটার্নটিকে ভয়ানক করে তোলে না। এমন কিছু ক্ষেত্রে রয়েছে যেখানে সিঙ্গলটন সেরা বিকল্প, যদিও সেগুলি বিরল।
কাইসারুলদী

47

এখানে আপনি একটি দুর্দান্ত উদাহরণ দেখতে পারেন। আপনি যদি ভার্চুয়াল পদ্ধতি ব্যবহার করেন তবে প্রোগ্রামটি জানবে যে রানটাইমে কী কার্যকর হয়। সংকলক সিআরটিপি বাস্তবায়ন করছে যা সংকলনের সময় সিদ্ধান্ত নেয় !!! এটি দুর্দান্ত পারফরম্যান্স!

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

25
খাঁটি ভার্চুয়াল পদ্ধতি ব্যবহার করে আপনি সংকলন সময়ের পরিবর্তে রানটাইমে উত্তরাধিকার সমাধান করছেন। সিআরটিপি সংকলন সময়ে এটি সমাধান করতে ব্যবহৃত হয় তাই কার্যকর করা দ্রুত হবে be
গুটিম্যাক

1
একটি বিমূর্ত লেখককে প্রত্যাশা করে একটি সরল ফাংশন তৈরির চেষ্টা করুন: আপনি কোথাও রাইটার নামে কোনও শ্রেণি নেই বলে আপনি এটি করতে পারবেন না, তবে আপনার পলিমারফিজমটি ঠিক কোথায়? এটি ভার্চুয়াল ফাংশনগুলির সাথে মোটেই সমান নয় এবং এটি অনেক কম কার্যকর।

22

সিআরটিপি হ'ল সংকলন-সময় বহুবচন প্রয়োগ করার একটি কৌশল। এখানে একটি খুব সাধারণ উদাহরণ। নীচের উদাহরণে, শ্রেণি ইন্টারফেসের 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
বেস ক্লাসে কোনও ডিফল্ট foo () কীভাবে প্রয়োগ করা যায় তার উদাহরণ যুক্ত করাও এটি উদাহরণস্বরূপ উপযুক্ত হতে পারে যা কোনও ডেরাইভড প্রয়োগ না করে থাকলে ডাকা হবে। একে একে বেসে অন্য কোনও নামে ফুও পরিবর্তন করে (যেমন কলার ()), বেসটিতে একটি নতুন ফাংশন ফু () যুক্ত করুন যা কাউটের "বেস" তৈরি করে। তারপরে প্রসেসফুর অভ্যন্তরে কলকারীকে () কল করুন
উইজুর্ড

@ উইজুরদ এই উদাহরণটি খাঁটি ভার্চুয়াল বেস শ্রেণির ফাংশন চিত্রিত করার জন্য আরও বেশি, যেমন আমরা প্রয়োগ করি foo()যা উদ্ভূত শ্রেণীর দ্বারা প্রয়োগ করা হয়।
ব্লুস্কিন

3
এটি আমার প্রিয় উত্তর, যেহেতু এটিও দেখায় যে এই প্যাটার্নটি ProcessFoo()ফাংশনের সাথে কেন কার্যকর।
Pietro

আমি এই void ProcessFoo(T* b)কোডটির বিন্দুটি পাই না, কারণ ডেরিভড এবং অ্যান্ডেল ডাইরাইভের সাথে এবং না করেই বাস্তবে প্রাপ্ত এটি এখনও কার্যকর হবে। IMHO এটি আরও আকর্ষণীয় হবে যদি প্রসেসফু কোনওভাবে টেম্পলেটগুলির ব্যবহার না করে।
গ্যাব্রিয়েল ডেভিলারস

1
@ গ্যাব্রিয়েল ডেভিলার্স প্রথমত, টেম্প্ল্যাটেডযুক্ত ProcessFoo()যে কোনও প্রকারের সাথে কাজ করবে যা ইন্টারফেস প্রয়োগ করে অর্থাত্ এই ক্ষেত্রে ইনপুট টাইপ টি নামক একটি পদ্ধতি থাকা উচিত foo()। দ্বিতীয়ত, ProcessFooএকাধিক প্রকারের সাথে কাজ করার জন্য অ-পরীক্ষামূলকভাবে পেতে, আপনি সম্ভবত আরটিটিআই ব্যবহার করবেন যা আমরা এড়াতে চাই। তদ্ব্যতীত, টেমপ্ল্লেটাইজড সংস্করণ আপনাকে ইন্টারফেসে সংকলন সময় পরীক্ষা করে থাকে।
ব্লুস্কিন

6

এটি সরাসরি উত্তর নয়, বরং সিআরটিপি কীভাবে কার্যকর হতে পারে তার একটি উদাহরণ ।


একটি ভাল কংক্রিট উদাহরণ CRTP হয় std::enable_shared_from_thisসি ++ 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

ঠিক যেমন নোট:

স্ট্যাটিক পলিমারফিজম (যা ডায়নামিক পলিমারফিজম পছন্দ করে তবে ভার্চুয়াল ফাংশন পয়েন্টার টেবিল ছাড়াই) সিআরটিপি ব্যবহার করতে পারে could

#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
দুঃখিত আমার খারাপ, স্ট্যাটিক_কাস্ট পরিবর্তনটির যত্ন নেয়। আপনি যদি কোনওভাবে কোণার কেসটি
odinthenerd

30
খারাপ উদাহরণ। এই কোডটি কোনও vtableসিআরটিপি ব্যবহার না করেই করা যেতে পারে । vtableসত্যিকার অর্থে কী সরবরাহ করা হয় তা হ'ল উদ্ভূত পদ্ধতিগুলিকে কল করতে বেস ক্লাস (পয়েন্টার বা রেফারেন্স) ব্যবহার করা। এটি এখানে সিআরটিপি দিয়ে কীভাবে করা হচ্ছে তা আপনার দেখা উচিত।
ইথেরিয়লোনে

17
আপনার উদাহরণে, Base<>::method ()এমনকি বলা হয় না, বা আপনি কোথাও বহুরূপী ব্যবহার করেন না।
মাইক এমবি

1
@Jichao, @MikeMB এর নোট অনুযায়ী, আপনি কল করা উচিত methodImplমধ্যে methodএর Baseএবং উদ্ভূত ক্লাসের নাম methodImplপরিবর্তেmethod
ইভান কুশ

1
আপনি যদি অনুরূপ পদ্ধতি ব্যবহার করেন () তবে এটি স্থিতিশীলভাবে আবদ্ধ এবং আপনার সাধারণ বেস শ্রেণির প্রয়োজন নেই। কারণ যাইহোক আপনি বেস ক্লাস পয়েন্টার বা রেফের মাধ্যমে বহুবিধভাবে এটি ব্যবহার করতে পারেন নি। কোডটি দেখতে এই জাতীয় হওয়া উচিত: # অন্তর্ভুক্ত <iostream> টেমপ্লেট <টাইপনেম টি> স্ট্রাক্ট রাইটার oid শূন্য রাইটিং () {স্ট্যাটিক_কাস্ট <টি *> (এটি) -> WritImpl (); }}; D ডেরিভড 1: পাবলিক রাইটার <ডেরিভড 1> {শূন্য রাইটইম্প্লি () {স্টাড :: কোট << "ডি 1"; }}; D ডেরিভেড 2: পাবলিক রাইটার <ডেরিভড 2> oid শূন্য রাইটইম্প্লি () d স্টাড :: কোট << "ডিইআর 2"; }};
বার্নি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.