Incepem o serie in care vom discuta despre diverse tehnici
de baza privind programarea in limbajul C++.
In acest snippet vom vedea cum putem simula mecanismul de polimorfism in momentul compilarii (compile time), folosind sabloane (templates) si o tehnica numita
CRT (curiously recurrent template).In mod normal, polimorfism-ul apare la runtime si se bazeaza pe mecanismul functiilor virtuale.
Sa vedem un examplu trivial:
// virtual .cpp
#include <iostream>
class Base
{
public:
virtual ~Base() {};
virtual void func() = 0;
};
class Derived1 : public Base
{
public:
void func()
{
std::cout << "Hello from Derived1" << std::endl;
}
};
class Derived2 : public Base
{
public:
void func()
{
std::cout << "Hello from Derived2" << std::endl;
}
};
class Proxy
{
public:
Proxy(Base* param) : m_param(param) {}
void SetParam(Base* param) { m_param = param; }
void func() { if (m_param) m_param->func(); }
private:
Base* m_param;
};
int main()
{
Derived1 myDerived1;
Derived2 myDerived2;
Proxy myProxy1(&myDerived1);
myProxy1.func();
Proxy myProxy2(&myDerived2);
myProxy2.func();
return 0;
}
Avem o clasa de baza denumita
Base, din care deriveaza 2 clase
Derived1 si
Derived2.
In clasa de baza
Base, avem declarata o functie membru virtual pura, numita
func Datorita faptului ca
func este virtual pura, clasele derivate din
Base vor trebui sa redefineasca functia
func, altfel se va obtine o eroare la compilare.
Clasa
Proxy are ca membru un pointer catre un obiect de tip
Base (sau, evident, derivat din
Base).
Daca compilam acest cod:
g++ virtual.cpp -o virtual
obtinem urmatorul output:
./virtual
Hello from Derived1
Hello from Derived2
Utilizarea functiilor virtuale aduce un grad mare de flexibilitate in design-ul programului, dar, aduce si un overhead: o indirectare in plus a apelului functiei virtuale prin tabelul de functii virtuale (vtable).
In marea majoritate a cazurilor, acest overhead este minor si poate fi ingnorat, dar, sunt aplicatii in care dorim performanta maxima si vrem sa eliminam acest overhead.
O solutie este utilizarea sabloanelor (templates) pentru a obtine un efect similar la
compile time.
Sa vedem cum modificam exemplul precedent pentru a beneficia de aceasta tehnica.
// crtp.cpp
#include <iostream>
template <typename T>
class Base
{
public:
virtual ~Base() {};
void func()
{
(static_cast<T*>(this))->func();
}
};
class Derived1 : public Base<Derived1>
{
public:
void func()
{
std::cout << "Hello from Derived1" << std::endl;
}
};
class Derived2 : public Base<Derived2>
{
public:
void func()
{
std::cout << "Hello from Derived2" << std::endl;
}
};
template <typename T>
class Proxy
{
public:
Proxy(Base<T>* param) : m_param(param) {}
void SetParam(Base<T>* param) { m_param = param; }
void func() { if (m_param) m_param->func(); }
private:
Base<T>* m_param;
};
int main()
{
Derived1 myDerived1;
Derived2 myDerived2;
Proxy<Derived1> myProxy1(&myDerived1);
myProxy1.func();
Proxy<Derived2> myProxy2(&myDerived2);
myProxy2.func();
return 0;
}
Clasa
Base este acum o clasa sablon dupa parametrul
TSa analizam implementare functiei membru
func in clasa
Basevoid func()
{
(static_cast<T*>(this))->func();
}
Aici, convertim pointerul
this catre un pointer la tipul
T (parametrul sablonului) si apelam functia
func membra a tipului
T. Ideea este faptul ca parametrul
T este o clasa care are definita o functie membru numita
func.
Bineinteles, vom avea grija sa instantiem corect sablonul
BaseSa analizam acum felul in care sunt declarate clasele derivate
Derived1 si
Derived2class Derived1 : public Base<Derived1>
{
public:
void func()
{
std::cout << "Hello from Derived1" << std::endl;
}
};
Clasa
Derived1 nu e declarata ca fiind o clasa sablon,
dar deriveaza din clasa sablon Base instantiata chiar dupa tipul Derived .
Aceasta constructie poate parea complet
neintuitiva unui programator novice, de aceea constructia este numita:
curiously recurrent template
Observam acum ca functia membra
func nu este declarata ca fiind virtuala in clasele derivate
Derived1 si
Derived2Daca apelam functia
func() pe un obiect de tipul
Derived1 (sau
Derived2) prin intermediul unui pointer sau referinte la o instanta a clasei de baza (
Base<Derived1> sau
Base<Derived2>), se va apela implementarea lui
func() din clasa de baza:
(static_cast<T*>(this))->func();
Dar, precum am vazut, in aceasta implementare noi convertim de fapt pointerul la instanta clasei de baza ((
Base<Derived1> sau
Base<Derived2>) catre un pointer de tipul paramtrului sablonului dupa care s-a facut instantierea (
Derived1 sau
Derived2)
Deci, ca efect, ajungem sa apelam implementarea lui func() din clasa derivata (Derived1 sau Derived2)In acest caz, clasa
Proxy este tot o clasa sablon care mentine ca membru un pointer catre
Base<T>.
Clasa
Proxy este instantiata in functia
main dupa
Derived1 si
Derived2Compilare:g++ crtp.cpp -o crtp
Executie:./crtp
Hello from Derived1
Hello from Derived2
Aceasta tehnica este folosita de catre multe framework-uri moderne scrise folosind C++, precum WTL, SmartWin, Loki ...
Este o tehnica
de baza in programarea moderna in limbajul C++.