Curiously Recurring Template Pattern

A curiously recurring template pattern (CRTP) egy sablonokat használó programnyelvi kifejezés. Azt jelenti, hogy egy X osztály egy X paraméterű sablonpéldányosításból származik. Általánosabban F-korlátos polimorfizmusként is ismert. Eredetileg C++ nyelven használták, de megvalósítható más nyelveken is.

Története szerkesztés

A technikát az 1980-as években formalizálták F-korlátos minősítésként.[1] Ettől függetlenül a CRTP nevet Jim Coplien adta 1995-ben, aki C++ sablon kódokban találta meg.[2] Továbbá a Timothy Budd által a Leda programozási nyelvhez készített programmintákban is felfedezte.[3] Néha fel-le öröklésként is emlegetik, mivel lehetővé teszi az osztályhierarchiák bővítését különböző bázisosztályok helyettesítésével.[4][5]

Általános formája szerkesztés

// The Curiously Recurring Template Pattern (CRTP)
template<class T>
class Base
{
    // methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
    // ...
};

A minta használati esetei különböző metaprogramozási módszerek, például a statikus polimorfizmus, vagy amelyekről Andrei Alexandrescu írt a Modern C++ Designban.[6] Emellett az adat, környezet és viselkedés paradigmának C++ megvalósítása is többször használja.[7]

Statikus polimorfizmus szerkesztés

Tipikusan a sablon bázisosztály számára előny, hogy a tagfüggvények törzse nem példányosul közvetlenül a deklaráció után, hanem addig hosszabb idő is eltelhet. Így a leszármazott osztály tagjait használhatja tagfüggvényeiben. Például:

template <class T> 
struct Base
{
    void implementation()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : public Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

Jegyezzük meg, hogy habár a fenti példa deklarálja a Base<Derived>::implementation() függvényt a Derived struct előtt, a függvény egészen addig nem példányosul, amíg meg nem hívja egy kódszakasz a Derived deklarálása után. Így amikor az implementáció példányosul, már ismert a Derived::implementation() deklarációja.

Ez a technika a virtuális függvényekhez hasonló hatást ér el a dinamikus polimorfizmus költségei nélkül. A CRTP-nek ezt a használatát szimulált dinamikus kötésnek is nevezik. A mintát sokszor használja a Windows ATL és WTL könyvtár.

A fenti példa kidolgozásához tekintsünk egy virtuális függvények nélküli bázisosztályt! Valahányszor hív egy tetszőleges tagfüggvényt, mindig a bázisosztály függvényét hívja. Származtatáskor mindent öröklünk, ami nincs felülírva. Ha a leszármazott egy olyan függvényt hív, amit a bázisosztályból örökölt, és az hív egy öröklött függvényt, vagy magát, vagy másikat, mindig az alaposztályban megírt függvény fog lefutni.

Ellenben ha a bázisosztály minden tagfüggvénye a CRTP-t használja minden függvényhíváshoz, a leszármazott osztály megfelelő tagfüggvénye fordítási időben kiválasztódik. Ezzel hatékonyan emulálja a virtuális függvényhívás rendszerét, anélkül, hogy megfizetné annak az árát: a függvényhívás adminisztrációját. Hátránya az, hogy futáskor már nem lehet módosítani ezt a választást.

Objektumszámláló szerkesztés

Egy objektumszámláló fő célja az, hogy nyomon kövesse egy adott osztály objektumainak létrejöttét és megszűnését. Ez egyszerűen megoldható CRTP-vel:

template <typename T>
struct counter
{
    static int objects_created;
    static int objects_alive;

    counter()
    {
        ++objects_created;
        ++objects_alive;
    }
    
    counter(const counter&)
    {
        ++objects_created;
        ++objects_alive;
    }
protected:
    ~counter() // objects should never be removed through pointers of this type
    {
        --objects_alive;
    }
};
template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );

class X : counter<X>
{
    // ...
};

class Y : counter<Y>
{
    // ...
};

Valahányszor létrejön egy X osztályú objektum, mindannyiszor meghívódik counter<X> konstruktora, ami megnöveli mind a létrejött, mind az élő számlálót. Valahányszor megszűnik egy objektum, az objektumszámláló csökkenti az élő mennyiséget. Fontos megjegyezni, hogy counter<X> és counter<Y> két külön osztály, ezért külön számolja az X és az Y osztályú objektumokat. Itt az osztályok csak a paraméterben különböznek, így sablon nélkül csak az osztály másolásával tudnánk a feladatot megoldani.

Polimorf láncolás szerkesztés

A metódusláncolás, ami paraméter idiómaként is ismert, közös szintaxis több metódus hívására az objektumorientált nyelvekben. Minden metódus visszaad egy objektumot, ami lehetővé teszi, hogy egy utasításban több metódust is hívjunk, és ne kelljen eltárolni a köztes eredményeket.

Ha az elnevezett paraméter objektum mintát egy objektumhierarchiára alkalmazzuk, akkor ez hibát okozhat. Legyen a bázisosztály:

class Printer
{
public:
      Printer(ostream& pstream) : m_stream(pstream) {}
 
      template <typename T>
      Printer& print(T&& t) { m_stream << t; return *this; }
 
      template <typename T>
      Printer& println(T&& t) { m_stream << t << endl; return *this; }
private:
      ostream& m_stream;
};

Könnyedén láncolhatjuk a kiírásokat:

Printer{myStream}.println("hello").println(500);

De ha leszármazott osztályt definiálunk:

class CoutPrinter : public Printer
{
public:
     CoutPrinter() : Printer(cout) {}
 
     CoutPrinter& SetConsoleColor(Color c) { ... return *this; }
};

akkor azonnal elveszítjük a konkrét osztályt, mihelyt meghívjuk a bázisosztály függvényét:

                             v-- we have a 'Printer' here, not a 'CoutPrinter'
CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!"); // compile error

Ez azért történik, mivel a 'print' a 'Printer' bázisosztály függvénye, és 'Printer' példányt ad vissza. A CRTP segít megoldani ezt a problémát, és képes implementálni a polimorf láncolást:[8]

// Base class
template <typename ConcretePrinter>
class Printer
{
public:
      Printer(ostream& pstream) : m_stream(pstream) {}
 
      template <typename T>
      ConcretePrinter& print(T&& t)
      {
          m_stream << t;
          return static_cast<ConcretePrinter&>(*this);
      }
 
      template <typename T>
      ConcretePrinter& println(T&& t)
      {
          m_stream << t << endl;
          return static_cast<ConcretePrinter&>(*this);
      }
private:
      ostream& m_stream;
};
 
// Derived class
class CoutPrinter : public Printer<CoutPrinter>
{
public:
     CoutPrinter() : Printer(cout) {}
 
     CoutPrinter& SetConsoleColor(Color c) { ... return *this; }
};
 
// usage
 CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");

Polimorf másoló konstruktor szerkesztés

Polimorfizmus használatakor néha objektumokat kell létrehozni a bázisosztály pointeréhez. Ennek egy gyakran használt módja a virtuális klónfüggvény hozzáadása, amit minden leszármazott osztályban definiálunk. A CRTP használatával a többszöri implementáció elkerülhető.

// Base class has a pure virtual function for cloning
class Shape {
public:
    virtual ~Shape() {};
    virtual Shape *clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape {
public:
    virtual Shape *clone() const {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP<Type>

// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

Ez lehetővé teszi, hogy négyzetek, körök és más alakzatok másolatait megkaphassuk a shapePtr->clone() használatával.

Hibalehetőségek szerkesztés

Egy általános közös bázisosztály, mint a fenti Shape használata nélkül a leszármazott osztályok mind egyedi típus lesz, és nem tárolhatóak homogén módon. Ezért ennél gyakoribb az öröklés egy virtuális destruktorral bíró megosztott alaposztályból.

Kapcsolódó szócikkek szerkesztés

Jegyzetek szerkesztés

  1. William Cook: F-Bounded Polymorphism for Object-Oriented Programming, 1989 [2015. február 10-i dátummal az eredetiből archiválva]. (Hozzáférés: 2017. május 5.)
  2. Coplien, James O. (February 1995). „Curiously Recurring Template Patterns”. C++ Report, 24–27. o.  
  3. Budd, Timothy. Multiparadigm programming in Leda. Addison-Wesley (1994). ISBN 0-201-82080-3 
  4. Apostate Café: ATL and Upside-Down Inheritance, 2006. március 15. [2006. március 15-i dátummal az eredetiből archiválva]. (Hozzáférés: 2016. október 9.)
  5. ATL and Upside-Down Inheritance, 2003. június 4. [2003. június 4-i dátummal az eredetiből archiválva]. (Hozzáférés: 2016. október 9.)
  6. Alexandrescu, Andrei. Modern C++ Design: Generic Programming and Design Patterns Applied. Addison-Wesley (2001). ISBN 0-201-70431-5 
  7. Lean Architecture: for agile software development. Wiley (2010). ISBN 0-470-68420-8 
  8. Use CRTP for polymorphic chaining. (Hozzáférés: 2017. március 15.)

Fordítás szerkesztés

Ez a szócikk részben vagy egészben a Curiously recurring template pattern című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.