C++/Szablony klas

Z Wikibooks, biblioteki wolnych podręczników.

< C++

Spis treści

[edytuj] Czym są?

Szablony (wzorce) są czymś podobnym do makr, tyle że wykonywane są przez kompilator, a nie przez preprocesor. Cechują je pewne własności, których jednak nie ma preprocesor, np. można tworzyć rekurencyjne wywołania. Ale zobaczmy najpierw taką sytuację: chcemy utworzyć klasę o nazwie Punkt o trzech współrzędnych: x, y, z. Potrzebujemy trzy różne implementacje. Jedna ma działać na liczbach typu unsigned int, druga na liczbach typu int, a trzecia na float. Pierwsze, o czym pomyślimy, to napisać coś takiego (jeśli dostrzeżesz błąd, to się nie przejmuj):

 // ...
 class PunktUInt
 {
    public:
       PunktUInt( unsigned _x, unsigned _y, unsigned _z )
       : x(_x), y(_y), z(_x)
       { }
 
       unsigned x, y, z;
 };
 // ...

I potem co? kopiujemy i zmieniamy unsigned na int:

 // ...
 class PunktInt
 {
    public:
       PunktInt( int _x, int _y, int _z )
       : x(_x), y(_y), z(_x)
       { }
 
       int x, y, z;
 };
 // ...

Następnie zamieniamy na float:

 // ...
 class PunktFloat
 {
    public:
       PunktFloat( float _x, float _y, float _z )
       : x(_x), y(_y), z(_x)
       { }
 
       float x, y, z;
 };
 // ...

Uff! Wreszcie napisaliśmy - pomyślisz sobie. Jednak pisanie wymaga trochę wysiłku. No dobrze, teraz tworzymy funkcję main:

 int main(void)
 {
    PunktInt A(0,-10,0);
    PunktUInt B(0,10,5);
 
    std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl;
    std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
 }

I oto po naszych ciężkich staraniach otrzymujemy dość ciekawy i niespodziewany wynik:

A(0,-10,0)
B(0,10,0)

Ojej! - zapewne krzykniesz. Musiał gdzieś się tu zjawić błąd. Trzeba znaleźć go. Aha, mamy go:

 // ...
       PunktUInt( unsigned _x, unsigned _y, unsigned _z )
       : x(_x), y(_y), '''z(_x)'''
 // ...

Zamiast z(_x) powinno być z(_z). I trzeba teraz wszystko poprawiać, w tym także resztę funkcji... Dobrze, że to tylko tyle. Ale na szczęście C++ daje nam prostszy sposób.

[edytuj] Wykorzystywanie szablonów

Napiszmy teraz jeszcze raz nasz program. Tym razem z wykorzystaniem szablonów:

 // ...
 template <class Typ>
 class Punkt
 {
    public:
       Punkt( Typ _x, Typ _y, Typ _z )
       : x(_x), y(_y), z(_x)
       { }
 
       Typ x, y, z;
 };
 // ...

Za pomocą template<class Typ> tworzymy nasz szablon. Parametrem jest typ, jaki chcemy użyć, tworzymy go poprzez <class Typ>. Teraz, aby utworzyć nasze punkty możemy zapisać:

 Punkt<int> A(0,-10,0);
 Punkt<unsigned> A(0,10,5);

Czyli nasz main będzie wyglądał tak:

 int main(void)
 {
    Punkt<int> A(0,-10,0);
    Punkt<unsigned> B(0,10,5);
 
    std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl;
    std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
 }

Ale nie podoba nam się ta notacja, bo na przykład program za bardzo zaczyna nam przypominać HTML. Co mamy zrobić? Nic innego, jak tylko przed funkcją main skorzystać z typedef:

 typedef Punkt<int> PunktInt;
 typedef Punkt<unsigned> PunktUInt;
 typedef Punkt<float> PunktFloat;

I tyle. Main zamieni nam się wtedy w pierwotną formę:

 int main(void)
 {
    PunktInt A(0,-10,0);
    PunktUInt B(0,10,5);
 
    std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl;
    std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
 }

- "No dobra, ale jak teraz uruchomię ten program to nadal mam ten sam, zły wynik" - powiesz. I masz rację, bo zobaczysz:

A(0,-10,0)
B(0,10,0)

Powód jest ten sam co poprzedni.

      Punkt( Typ _x, Typ _y, Typ _z )
      : x(_x), y(_y), z(_x)
      { }

Musimy zamienić z(_x) na z(_z) i będzie wszystko w porządku. Tylko tyle. Nie wierzysz? Ale to jest prawda. To zobacz cały nasz program powinien wyglądać w ten sposób:

 #include <iostream>
 
 template <class Typ>
 class Punkt
 {
    public:
       Punkt( Typ _x, Typ _y, Typ _z )
       : x(_x), y(_y), z(_z)
       { }
 
       Typ x, y, z;
 };
 
 typedef Punkt<int> PunktInt;
 typedef Punkt<unsigned> PunktUInt;
 typedef Punkt<float> PunktFloat;
 
 
 int main(void)
 {
    PunktInt A(0,-10,0);
    PunktUInt B(0,10,5);
 
    std::cout << "A(" << A.x << "," << A.y << "," << A.z << ")" << std::endl;
    std::cout << "B(" << B.x << "," << B.y << "," << B.z << ")" << std::endl;
 }

[edytuj] Szablony z wieloma parametrami

Szablon może także posiadać więcej niż jeden parametr. Na przykład chcielibyśmy napisać klasę Para, zawierającą dwa elementy pierwszy o nazwie pierwszy, a drugi o nazwie drugi, jednakże nie wiemy jakie mają one mieć typ. Możemy to zrobić w ten sposób:

 #include <iostream>
 
 template <class T1, class T2>
 class Para
 {
    public:
       Para()
       { }
 
       Para( T1 _a, T2 _b )
       : pierwszy(_a), drugi(_b)
       { }
 
       T1 pierwszy;
       T2 drugi;
 };
 
 int main(void)
 {
     // tworzymy nasz obiekt
     Para<char*,int> zmienna("Liczba",10);
 
     std::cout << zmienna.pierwszy << " " << zmienna.drugi << std::endl;
     return 0;
 }

Za pomocą template<class T1, class T2> utworzyliśmy szablon o dwóch parametrach.

[edytuj] Można też liczby

Szablonem może być także liczba. Zilustrujmy to przykładem:

 #include <iostream>
 
 template <class T, int N>
 class Tablica
 {
     public:
        Tablica()
        { }
 
        T &operator[]( int i )
        {
           return tabl[i];
        }
 
     private:
        T tabl[N];
 };
 
 int main(void)
 {
    Tablica<int,10> A;
 
    for ( int i=0; i<10; ++i )
    {
        A[i]=100+i;
    }
 
    for ( int i=0; i<10; ++i )
    {
       std::cout << "A[" << i << "]=" << A[i] << std::endl;
    }
    return 0;
 }