C++ Sablonok

Tegyük fel, hogy írnunk kell egy programot, amely tetszőleges típusú elemet tárol illetve rendez. Ez a feladat, ha a szimpla C szemszögéből közelítjük meg, akkor szinte megvalósíthatatlannak tűnik. Lenne egy sor void* mutatónk, illetve egy más sornyi azonosítónk, mely az elemek típusát tárolja, és még sornyi függvényünk, mely különböző adattípusok összehasonlítását végzi el. Gigászi feladat.

A C++ erre a feladatra vezette be a sablonokat. Érdekesség, hogy C++ legelső kiadása nem tartalmazta ezt a nagyon hasznos tulajdonságot, viszont a programozók visszajelzéseiből alapján ez hasznosnak ítéltetett, és beiktaták az elkövetkező kiadásokba. A C++ sablonok lehetőséget nyújtanak, hogy egy absztrakt paraméterezési módszerrel (illetve változó típusdeklarálási módszerrel) tetszőleges típusú függvényeket illetve osztályokat hozzunk létre.

A sablonok nagyon hasznosak egy programozó életében, hisz egy (helyesen) megírt sablon bármilyen felhasználó által definiált, illetve alap típusra működni fog. Tekintsük példaként, hogy megírtunk egy CArray sablont, mely adatokat tárol illetve rendez. Ennek a sablonnak az alapján létrehozhatunk egy int tömböt, egy float tömböt, és amennyibe megoldjuk két CEmployee osztály összehasonlítását, egy CEmployee tömböt.

Függvény sablonok

Egy függvény sablon egy végtelen elemű felüldefiniált függvényhalmazt hoz létre. (Hogy mi is az a felüldefiniált függvény? Amikor definiálunk egy függvényt, egy bizonyos parametrizálással, majd ezután ugyanazzal a névvel definiálunk egy másik függvény, melynek a parametrizálása különböző. Ez egy nagyon hasznos technológia, hogy egyszerűbben megérthető kódot tudjunk írni.) Egy függvénysablont a következőképpen definiálunk:

template<class T, class T1, ...> return_type function (T par1, T par2, ... )

ahol a template illetve a class egy egy C++ kulcsszó, a class T (illetve az összes többi) az absztrakt adattípusokat képviselik (típus változó a rendes neve), a return_type tetszőleges adattípus, lehet nyugodtan a definiált T (a class nélkül) a function a függvény neve, T par1, illetve az összes többi paraméter, melyeket én három ponttal jelöltem, a függvény paraméterei.

Vegyük például a min függvényt, amely mint tudjuk, két elem minimumát kell visszatérítse:

template <class T> T min(T a, T b) {

    return a<b?a:b;

}

A függvények esetében a sablon paraméterek típusát nem definiáljuk explicit módon (vagyis nem mondjuk meg, hogy most használjuk azt a min függvényt, mely int típusú paramétereket vár el). A C++ automatikusan létrehozza a használt típusokból a sablon alapján a megfelelő függvényt. Tekintsük például:

int i, j, k;

double u, v, w;

char a, b, c;

...

k = min(i,j);

v = min(u,v);

c = min(a,b);

Ezen esetben három függvény lesz generálva, mégpedig:

int min(int, int);

double min(double, double);

char min(char, char);

Amennyiben egy osztálynak definiáljuk a < operátort, a min függvényünk osztálytípusú változókra is működni fog. Viszont a következő függvényhívás hibát generál:

k = min(i,u);

Hogy miért, azt könnyen ki lehet találni: nem definiáltunk olyan sablont, mely két absztrakt típust képes kezelni, és ezek közül az egyiket visszatéríti. A C++ fordító függvénysablon esetén nem végez automatikus adatkonverziót, viszont bármely sablonfüggvényt felül tudjuk definiálni.

Osztálysablonok

Osztálysablonokat a következőképpen lehet definiálni:

template<osztálysablon paraméterek> class osztálynév {

};

Az osztálysablon paraméterek egyrészt a fent említett class T típusúak lehetnek, másrészt bevezethetünk alaptípusokat is, amelyek használata nagyban megkönnyíti munkánkat. Tekintsük például a következő osztályt:

template<class T, int size> class CArray {

public:

      CArray(T defval);

      ~CArray();

      T& operator [] (int idx);

private:

      T els[size];

};

Mint látjuk, ez az osztály nemcsak egy típust vár el, hanem egy int típusú változót is, amelynek megadása kötelező. Általában ezt a típusú definíciót akkor használjuk, amikor statikusan van szükségünk size (a mi esetünkbe) adatra, jelen esetben látjuk a size elemű vektort deklarálva.

Nézzük tovább a Carray osztály konstruktorának illetve destrukotrának a deklarálását:

template<class T, int size>

CArray<T,size>::CArray(T defval = 0) {

      for(int i=0;i<size;i++) {

            els[i]=defval;

      }

}

template<class T, int size>

CArray<T,size>::~CArray() {

      //do cleanup code here 

}

Mint kitűnik, elsősorban meg kell adnunk a sablon típusát, majd megmondjuk a fordítónak, hogy ez a konstruktor illetve destruktor abba a sabloncsaládba tartozik, amelynek típusparaméterei a T illetve a size. Ezek után jöhet magának a konstruktor illetve destruktornak a definiálása. Mivel ezek különleges függvények, nincs visszatérési értékük. Nézzük viszont az operator függvény deklarálását:

template<class T, int size>

T& CArray<T,size>::operator [](int idx) {

      return els[idx];

}

Látjuk hogy a visszatérési érték a sablon típus után, és az osztályazonosító elé került. Normál függvényeknél is így járunk el.

Amikor sablon osztályból származtatunk, a következőképpen definiáljuk a származtatott osztályt:

template<int size,int defval>class CIntArray:public CArray<int,size> {

public:

      CIntArray();

};

Ezzel létrehoztunk egy CIntArray osztályt, melyet a CArray<int,size> osztályból származtattunk. Az osztály konstruktor függvénye a következőképpen lesz deklarálva:

template<int size,int defval>

CIntArray<size,defval>::CIntArray():CArray<int,size>(defval) {

    // init sutff

}

Egy sablon alapján objektumokat a következőképpen hozunk létre:

CIntArray<10,1> array1;

Ezzel létrehoztunk egy olyan objektumot, mely tíz darab int-tet tároló tömb lesz, amely elemeinek alapértelmezett értéke 1.

Általában a mindennapi munkához elég ez a mélység, amire most behatoltunk a sablonok világába, de nem árt azért elgondolkodni a következő pár definíción:

template<class T,  class T2 = T > class Test {

};

létrehoz egy olyan sablont, amely két típusparamétert vár el, de a második megegyezik az elsővel. Ezért amikor a Test osztályból példányosítunk, elégséges a következő:

Test <int> test;

A típusparamétereknek is lehet alapértelmezett értéke, és ők is lehetnek származtatott típusúak, ezt mutatja a következő példa:

template<int size =20,  class T2 = CIntArray<size , 0> > class TestA {

};                                 

Elégséges, ha így példányosítunk:

TestA<> test2;

Konklúzióként pedig csak annyit tudok mondani, hogy használjuk a sablonokat. Igaz, picit nehézkes a gondolatmenetük több kódot kell írni, viszont nagyon rugalmasak, és sok tekintetben megkönnyítik munkánkat.

Deák Ferenc