curiously recurring template pattern (tsz. curiously recurring template patterns)
Ez elsőre furcsán hangzik, de valójában egy nagyon erős eszköz arra, hogy:
template <typename Szarmazott>
class Alap {
public:
void felulet() {
static_cast<Szarmazott*>(this)->muvelet();
}
};
class Gyerek : public Alap<Gyerek> {
public:
void muvelet() {
std::cout << "Gyerek::muvelet() hívva\n";
}
};
Itt a Gyerek
osztály öröklődik a Alap<Gyerek>
osztályból – vagyis önmagából egy példányosított formából. Ez a “furcsán ismétlődő” rész.
A Alap
osztályból meghívjuk a muvelet()
függvényt, de az valójában a Gyerek osztályhoz tartozik. Mivel ez mind sablonokkal és static_cast
-tal történik, az egész fordítási időben eldől, és nincs futásidejű költség (ellentétben a virtuális függvényekkel).
💡 Használat | Mit nyújt? |
---|---|
Statikus polimorfizmus | virtual helyett gyorsabb alternatíva, de nem futásidőben
|
Kód újrahasználat | Sablon-alapú függvényekkel többféle osztályra |
Mixin-ek | Külön viselkedés hozzáadása osztályhoz örökléssel |
Kompilációs idő optimalizáció | Fordításkor történő döntéshozatal, elágazás |
Futásidő minimalizálása | Nincs vtable vagy pointer dereferálás |
operator==
Tegyük fel, hogy sok olyan osztályunk van, ahol meg kell valósítani az összehasonlító operátort (==
). Ahelyett, hogy minden osztályban újraírnánk, használhatjuk a CRTP-t:
template <typename T>
class Osszehasonlithato {
public:
bool operator==(const T& masik) const {
return static_cast<const T*>(this)->ertek() == masik.ertek();
}
};
class Pont : public Osszehasonlithato<Pont> {
int x;
public:
Pont(int x) : x(x) {}
int ertek() const { return x; }
};
Itt a Pont
osztály automatikusan örökli az ==
operátort az Osszehasonlithato
sablonból, és csak az ertek()
függvényt kell megvalósítania.
A CRTP segítségével különböző viselkedéseket adhatunk hozzá osztályokhoz, “mixin”-ként.
template <typename T>
class Kiirhato {
public:
void kiir() const {
std::cout << static_cast<const T*>(this)->toString() << std::endl;
}
};
class Ember : public Kiirhato<Ember> {
std::string nev;
public:
Ember(std::string n) : nev(n) {}
std::string toString() const {
return "Nev: " + nev;
}
};
Az Ember
osztály most már rendelkezik egy kiir()
metódussal, amit a Kiirhato
sablon biztosít.
Ez a minta lehetővé teszi, hogy különböző viselkedéseket adjunk osztályokhoz sablon paraméterként.
template <typename LoggerPolicy>
class Logger : public LoggerPolicy {
public:
void log(const std::string& uzenet) {
this->ir(uzenet);
}
};
class KonzolLogger {
public:
void ir(const std::string& uzenet) {
std::cout << " " << uzenet << std::endl;
}
};
Logger<KonzolLogger> logger;
logger.log("Szia, világ!");
Tulajdonság | virtual
|
CRTP |
---|---|---|
Futásidejű költség | Igen (vtable, pointer) | Nincs (inline, static) |
Típusdöntés ideje | Futásidő | Fordítási idő |
Rugalmasság | Dinamikus | Kompiláció idején kötött |
Többféle viselkedés | Nehézkes | Könnyű sablon paraméterekkel |
Használhatóság | Interfészeknél elengedhetetlen | Optimalizációs célokra ideális |
Lehetőség van arra, hogy több viselkedést adjunk egy osztályhoz CRTP-n keresztül:
template <typename T>
class Szamolhato {
public:
int dupla() const {
return static_cast<const T*>(this)->ertek() * 2;
}
};
template <typename T>
class Leirhato {
public:
std::string leiras() const {
return "Érték: " + std::to_string(static_cast<const T*>(this)->ertek());
}
};
class Szam : public Szamolhato<Szam>, public Leirhato<Szam> {
int val;
public:
Szam(int v) : val(v) {}
int ertek() const { return val; }
};
virtual
kulcsszót minden helyzetben.
A CRTP egy haladó C++ sablon technika, amely: