Wykład 5. C++: szablony
◮
Szablony to technika realizacji polimorfizmu na innym poziomie niż za pomocą funkcji wirtualnych i dziedziczenia.
◮
Mechanizm ten można rozumieć jako ’inteligentniejsze’
makrodefinicje.
◮
Szablony nie są w bezpośredni sposób związane z
programowaniem obiektowym.
przykład problemu: definiujemy funkcję porównującą...
...liczby całkowite:
int compare(int a, int b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
przykład problemu: definiujemy funkcję porównującą...
...liczby całkowite:
int compare(int a, int b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
...liczby rzeczywiste:
int compare(float a, float b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
przykład problemu: definiujemy funkcję porównującą...
...liczby całkowite:
int compare(int a, int b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
...liczby rzeczywiste:
int compare(float a, float b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
... i tak samo dla każdego innego typu
przykład problemu: definiujemy funkcję porównującą...
...liczby całkowite:
int compare(int a, int b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
...liczby rzeczywiste:
int compare(float a, float b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
... i tak samo dla każdego innego typu
... pod warunkiem, że ma zdefiniowany operator <
przykład problemu: definiujemy funkcję porównującą...
...liczby całkowite:
int compare(int a, int b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
...liczby rzeczywiste:
int compare(float a, float b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
...pudełka class Pudelko {
public:
float dl,sz,wy;
Pudelko(float d, float s, float w) : dl(d), sz(s), wy(w) {}
int operator<(Pudelko& p) { return dl*sz*wy < p.dl*p.sz*p.wy; } };
int compare(Pudelko a, Pudelko b)
{ if(a<b) return 1; else if(b<a) return -1; else return 0; }
podstawowe informacje
◮
szablon funkcji jest sparametryzowaną definicją funkcji
◮
szablon funkcji definiuje się w zakresie globalnym
◮
parametrem szablonu funkcji jest typ (klasa), bądź lista typów
◮
wszystkie parametry szablonu muszą wystąpić w opisie argumentów funkcji szablonowej (kompilator odnajduje szablon na podstawie wywołania funkcji)
Składnia:
template< opis˙parametrów >
definicja funkcji
przykład
class Pudelko {
public:
float dl,sz,wy;
Pudelko(float d, float s, float w) : dl(d), sz(s), wy(w) {}
int operator<(Pudelko& p) { return dl*sz*wy < p.dl*p.sz*p.wy; } };
template <class T>
int compare(T a, T b) {
if(a<b) return -1; else if(b<a) return 1; else return 0;
}
int main() {
cout
<< compare(1,2) << endl //-1
<< compare(1.5,2.5) << endl //-1
<< compare(’a’,’b’) << endl //-1
<< compare(Pudelko(1.0,2.0,2.0),Pudelko(2.0,1.5,4.2)) << endl;//-1 }
parametry
◮
parametrem szablonu funkcji jest typ (klasa), bądź lista typów
◮
wszystkie parametry szablonu muszą wystąpić w opisie argumentów funkcji szablonowej (kompilator odnajduje szablon na podstawie wywołania funkcji)
◮
oprócz obowiązkowego użycia parametrów szablonu w opisie argumentów funkcji szablonowej, można ich używać w dowolnym miejscu w definicji tej funkcji (jako nazwy typu).
template<class S, class T>
T* jakasfunkcja(S a, T b) {
T *l = new(T);
int r=sizeof(S);
...
return l;
}
◮
jedynym ograniczeniem, jakie w definicji szablonu można nałożyć na typy argumetów funkcji szablonowej jest by niektóre z nich były takie same, np. szablon
template <class K, class L, class M>
void f(K a, L b, K c, M d) ...
pasuje do wywołania f(3,’e’,5,"Ala") ale do wywołania
f(3,’e’,’a’,"Ala")
nie.
jak to działa
◮
natrafiając na wywołanie funkcji, kompilator sprawdza, czy istnieje funkcja o podanej nazwie, liczbie i typie argumentów
◮
jeśli nie, sprawdza, czy istnieje szablon pozwalający taką funkcję wygenerować, jeśli tak – generuje odpowiednią funkcję
◮
jeśli nie, sprawdza czy isnieje funkcja którą można dopasować
dokonując konwersji typów argumentów, jeśli tak, dokonuje
wiązania z zastosowaniem konwersji typów
sytuacje wyjątkowe
◮
często zdarza się, że funkcje wygenerowane z szablonu będą działać poprawnie (lub w ogóle będą poprawne) w większości przypadków, ale nie we wszystkich
◮
szablon z poprzedniego przykładu nie będzie działał poprawnie dla typu char*
◮
nie nadaje się on w ogóle np. dla typu
class Data { public:
int dzien, miesiac, rok;
}
za względu na brak operatora <
cout
<< compare(1,2) << endl // -1
<< compare(1.5,2.5) << endl // -1
<< compare(’a’,’b’) << endl // -1
<< compare(Pudelko(1.0,2.0,2.0),Pudelko(2.0,1.5,4.2)) << endl // -1
<< compare("Ala","Ola") << endl // 1 (!)
<< compare((string)"Ala",(string)"Ola") << endl; // -1
funkcje specjalizowane
◮
wyjątki od sposobu generownia funkcji zdefiniowanego przez szablon można zdefiniować pisząc funkcję specjalizowaną
◮
funkcja specjalizowana to funkcja, której nazwa i typy
parametrów pasują do szablonu, tyle że jest jest to normalna
funkcja
funkcje specjalizowane
template <class T>
int compare(T a, T b) {
if(a<b) return -1; else if(b<a) return 1; else return 0;
}
int compare(const char* a, const char* b) {
return strcmp(a,b);
}
int main() {
cout << compare(1,2) << endl // -1
<< compare(1.5,2.5) << endl // -1
<< compare(’a’,’b’) << endl // -1
<< compare(Pudelko(1.0,2.0,2.0),Pudelko(2.0,1.5,4.2)) << endl // -1
<< compare("Ala","Ola") << endl // -1
<< compare((string)"Ala",(string)"Ola") << endl; // -1 }
podstawowe informacje
◮
szablon klasy jest sparametryzowaną definicją klasy
◮
szablon klasy definiuje się w zakresie globalnym Składnia:
template< opis˙parametrów >
class nazwaklasy {
...
};
przykład (bardzo typowy)
Szablon klasy Stos, którego parametrem jest typ elementów przechowywanych na stosie.
template <class T>
class Stos {
public:
Stos() : n(0) }
Stos& push(T e) { dane[n++]=e; return *this; } // dodanie elementu
T pop() { return dane[--n]; } // pobranie elementu
int empty() { return n==0; } // test czy pusty
operator int() { return n; } // dodatkowy bajer
private:
T dane[100];
int n;
};
klasa szablonowa
◮
po zdefniowaniu szablonu klas
template< opis˙parametrów >
class nazwaklasy {
...
};
nazwą klasy szablonowej jest nazwaklasy<parametry>
◮
posługujemy się nią jak zwykłymi nazwami klas/typów
◮
w momencie napotkania takiej nazwy, kompilator generuję
definicję klasy szablonowej dla podanych parametrów (jeśli
wcześniej już tego nie zrobił)
użycie klasy szblonowej
int main() {
Stos<char> stosznakow; // generowana jest klasa Stos<char>
Stos<int> stosliczb; // generowana jest klasa Stos<int>
stosznakow.push(’A’).push(’l’).push(’a’);
stosliczb.push(1).push(2).push(3);
Stos<char> drugistosznakow=stosznakow; // klasa Stos<char> już jest
while(stosznakow) // bajer
{
cout << stosznakow.pop();
}
cout << endl;
while(stosliczb) {
cout << stosliczb.pop();
}
cout << endl;
}
parametry
◮
Szablon klasy może mieć wiele parametrów (separatorem jest przecinek)
◮
Parametrem szablonu klasy może być:
◮
typ (class nazwa)
◮
wartość całkowita stała (int nazwa)
template < class Typ, int rozmiar >
◮
także stałe wyrażenie będące adresem obiektu globalnego lub
funkcji globalnej (rzadsze zastosowanie)
definicje metod klasy szablonowej poza ciałem klasy
◮
definicje metod klasy szablonowej umieszczone poza ciałem klasy są w istocie szablonami definicji metod
◮
mają postać taką jak szablony funkcji, z tą oczywiście różnicą, że ich nazwę poprzedza się nazwą klasy szablonowej
◮
podobnie też jak w przypadku szablonów funkcji, można
definiować metody specjalizowane
definicje metod klasy szablonowej poza ciałem klasy
template <class T>class Stos {
public:
Stos();
Stos& push(T e);
T pop();
itd private:
T dane[100];
int n;
};
template<class T>
Stos<T>::Stos() : n(0) {}
template<class T>
Stos<T>& Stos<T>::push(T e) { dane[n++]=e; return *this; }
template<class T>
T Stos<T>::pop() { return dane[--n]; } itd.
definicje metod klasy szablonowej poza ciałem klasy (lupa)
◮
są to szablony metod
template<class T>
Stos<T>::Stos() : n(0) {}
template<class T>
Stos<T>& Stos<T>::push(T e) { dane[n++]=e; return *this; }
template<class T>
T Stos<T>::pop() return dane[--n];
itd.
definicje metod klasy szablonowej poza ciałem klasy (lupa)
◮
nieodłączną częścią nazwy klasy szablonowej jest parametr(y)
template<class T>
Stos<T>::Stos() : n(0) {}
template<class T>
Stos<T>& Stos<T>::push(T e) { dane[n++]=e; return *this; }
template<class T>
T Stos<T>::pop() return dane[--n];
itd.
definicje metod klasy szablonowej poza ciałem klasy (lupa)
◮
nazwa konstruktora (destruktora) nie jest nazwą klasy — bez parametu
template<class T>
Stos<T>::Stos() : n(0) {}
template<class T>
Stos<T>& Stos<T>::push(T e) { dane[n++]=e; return *this; }
template<class T>
T Stos<T>::pop() return dane[--n];
itd.
parametry nie będące typami – przykład
W szablonie stosów dodajemy parametr określający rozmiar.
template <class T, int max>
class Stos {
public:
Stos() : n(0) {}
Stos& push(T e) { dane[n++]=e; return *this; } T pop() { return dane[--n]; }
int empty() { return n==0; } operator int() { return n; }
private:
T dane[max];
int n;
};
int main() {
Stos<char,20> stos;
stos.push(’A’).push(’l’).push(’a’);
while(stos) { cout << stos.pop(); } cout << endl;
}
klasa szablonowa jako klasa bazowa
◮
Klasa szablonowa może być klasą bazową innej klasy.
◮
Klasa szablonowa może być klasą bazową w szablonie klasy.
template<class T>
class K {
...
};
class L : K<int>
{ ...
}
template<class S>
class M : K<int>
{ ...
}
klasa szablonowa jako klasa bazowa
◮
Klasa szablonowa może być klasą bazową innej klasy.
◮
Klasa szablonowa może być klasą bazową w szablonie klasy.
template<class T>
class K {
...
};
class L : K<int>
{ ...
}
template<class S>
class M : K<int>
{ ...
}
szablon klasy jako klasa bazowa
◮
szablonu klasy można użyć jako klasy bazowej w innym szablonie klasy
◮
parametry szablonu klasy bazowej muszą być parametrami szablonu klasy pochodnej albo muszą mieć ustaloną wartość
template<class T, int max>
class Stos {
...
};
template<class S, int max>
class lepszystos : K<S,max>
{ ...
}
template<class S>
class stosstulementowy : K<S,100>
{ ...
}
szablon klasy jako klasa bazowa
◮
szablonu klasy można użyć jako klasy bazowej w innym szablonie klasy
◮
parametry szablonu klasy bazowej muszą być parametrami szablonu klasy pochodnej albo muszą mieć ustaloną wartość
template<class T, int max>
class Stos {
...
};
template<class S, int max>
class lepszystos : K<S,max>
{ ...
}
template<class S>
class stosstulementowy : K<S,100>
{ ...
}