C++/Szablony klas
Czym są?
edytujSzablony (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). Mamy przykładową klasę:
// ...
class PunktUInt
{
public:
PunktUInt( unsigned argX, unsigned argY, unsigned argZ )
: x(argX), y(argY), z(argX)
{ }
unsigned x, y, z;
};
// ...
I potem co? kopiujemy i zmieniamy unsigned na int:
// ...
class PunktInt
{
public:
PunktInt( int argX, int argY, int argZ )
: x(argX), y(argY), z(argZ)
{ }
int x, y, z;
};
// ...
Następnie zamieniamy na float:
// ...
class PunktFloat
{
public:
PunktFloat( float argX, float argY, float argZ )
: x(argX), y(argY), z(argX)
{ }
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 argX, unsigned argY, unsigned argZ )
: x(argX), y(argY), '''z(argX)'''
// ...
Zamiast z(argX) powinno być z(argZ). 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.
Wyróżniamy różne możliwości szablonów:
Wykorzystywanie szablonów
edytujNapiszmy teraz jeszcze raz nasz program. Tym razem z wykorzystaniem szablonów.
// ...
template <typename T>
class Punkt
{
public:
Punkt( T argX, T argY, T argZ )
: x(argX), y(argY), z(argX)
{ }
T x, y, z;
};
// ...
Za pomocą template<typename T> tworzymy nasz szablon. Parametrem jest typ, jaki chcemy użyć, tworzymy go poprzez <typename T>. 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;
}
Powiesz:
No dobra, ale jak teraz uruchomię ten program to nadal mam ten sam, zły wynik.
I masz rację, bo zobaczysz:
A(0,-10,0) B(0,10,0)
Powód jest ten sam co poprzedni.
Punkt( T argX, T argY, T argZ )
: x(argX), y(argY), '''z(argX)'''
{ }
Musimy zamienić z(argX) na z(argZ) 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 <typename T>
class Punkt
{
public:
Punkt( T argX, T argY, T argZ )
: x(argX), y(argY), z(argZ)
{ }
T 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;
}
Szablony z wieloma parametrami
edytujSzablon może także mieć więcej niż jeden parametr. Na przykład chcielibyśmy posługiwać się parami obiektów. Należy więc napisać klasę Para, zawierającą dwa elementy: pierwszy o nazwie pierwszy, a drugi o nazwie drugi, jednakże nie wiemy z góry, jakie mają one mieć typy. Możemy to zrobić w ten sposób:
#include <iostream>
#include <string>
template <typename T1, typename 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<std::string,int> zmienna("Liczba",10);
std::cout << zmienna.pierwszy << " " << zmienna.drugi << std::endl;
return 0;
}
Za pomocą template<typename T1, typename T2> utworzyliśmy szablon o dwóch parametrach.
Deklaracja zmiennej zmienna określa jej typ poprzez skonkretyzowanie typów w szablonie, pośrednio więc określa też, jakie typy będą miały składowe tej zmiennej.
Można też liczby
edytujParametrem szablonu może być także liczba. Zilustrujmy to przykładem:
#include <iostream>
#include <cstddef>
template <typename T, std::size_t N>
class Tablica
{
public:
T &operator[]( std::size_t 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;
}
W powyższym przykładzie użyto typu std::size_t, zadeklarowanego w pliku nagłówkowym dołączanym dyrektywą #include <cstddef>.