You are on page 1of 25

Wzorce/szablony

Wzorce klas – dlaczego?


• Pisząc programy często korzystamy z abstrakcyjnych typów
danych, takich jak stos, kolejka czy drzewo. Implementacje
takich typów mogą być prawie identyczne, na przykład klasy
lista_liczb i lista_znaków mogą różnić się tylko typem
elementu przechowywanego na liście.
• Wzorzec klasy to sposób na napisanie uogólnionej .
sparametryzowanej klasy — klasy której parametrem będzie
typ, bądź inna klasa. Można napisać wzorzec listy, a potem w
zależności od tego czego aktualnie potrzebujemy
zadeklarować listę znaków, bądź listę figur.
• Wzorce są doskonalszym i wygodniejszym sposobem (od
stosowania preprocesora) tworzenia rodzin typów i funkcji.
• Na wzorcach opartych jest wiele technik obiektowych oraz są
podstawą nowoczesnego programowania generycznego.
• Wzorce nazywane są również szablonami (ang. templates).
Wzorce klas – jak deklarować?
template <class T> // wzorzec, którego argumentem jest typ T
class Stack // wzorzec klasy stos
{
T* v; // początek stosu
T* p; // koniec stosu
int size; // pojemność stosu
public:
Stack(int r) { v = p = new T[size = r]; }
// konstruktor z argumentem:
// pojemność stosu
~Stack() { delete[]v; } // destruktor
void push(T a) { *p++ = a; } // wstaw na stos
T pop() { return *--p; } // zdejmij ze stosu
int getSize() const { return p - v; }
// ile elementów typu T jest na stosie
};
Wzorce klas – jak deklarować?
template <class T> // deklarujemy wzorzec,
// którego argumentem
// jest typ T
// od C++11: <typename T>
class Stack // wzorzec klasy stos

• Typu (klasy) T można używać do końca deklaracji klasy stos tak


jak każdego innego dostępnego typu lub klasy.
• W zakresie deklaracji wzorca Stack używanie pełnej nazwy
typu Stack<T> jest nadmiarowe zarówno przy definicji
metod wewnątrz klasy jak i poza nią, wystarczy Stack
zarówno dla określenia klasy jak i nazw konstruktorów i
destruktora.
• Poza deklaracją odwołujemy się do typu wywiedzionego ze
wzorca jako Stack<T>
Wzorce klas – jak deklarować?
• Mając wzorzec klasy stos można deklarować stosy
różnych elementów przekazując typ elementu jako
aktualny parametr wzorca.
• Składnia nazwy typu wywiedzionego ze wzorca jest
następująca:
nazwa_klasy_wzorca<argument_wzorca>

– Nazwa klasy stosu liczb:


Stack<int>

– Nazwa klasy stosu wskaźników do figur:


Stack<figure *>
Wzorce klas
• Deklaracja:
Stack<int> numbers(100);

• to deklaracja obiektu o nazwie numbers,


• należącego do klasy Stack<int>,
• oraz wywołanie konstruktora Stack<int>(100).

• Nazwy klasy utworzonej ze wzorca można używać tak


samo jak nazwy każdej innej klasy,
różnica to inna składnia nazwy.
Stack<figure*> spf(200); // stos wskaźników do figur zdolny
// pomieścić 200 wskaźników
Stack<Point> sp(400); // stos punktów o pojemności 400

void f(Stack<complex> &sc) // funkcja f, której argumentem jest


// referencja do stosu liczb zespolonych
{
sc.push(complex(1, 2)); // wstaw do stosu

complex z = 2.5 * sc.pop(); // zdejmij liczbe ze stosu,


// pomnóż ją i przypisz
Stack<int> *p = 0; // deklaracja wskaźnika do
// stosu liczb całkowitych
p = new Stack<int>(800); // konstrukcja stosu 800 liczb int
for (int i = 0; i<400; i++) // 400 razy
{
p->push(i); // wstaw liczbe i do stosu liczb
sp.push(Point(i, i + 400 // oraz punkt do stosu punktów;
}
delete p; // destrukcja stosu liczb
}
Walidacja wzorca
• Kompilator sprawdza poprawność wzorca w
momencie jego użycia, a więc błędy w samej
deklaracji wzorca mogą pozostać niezauważone aż do
momentu próby wykorzystania wzorca.
– Poprawna kompilacja pliku zawierającego wzorzec nie
oznacza że wzorzec nie zawiera błędów.
– Częstą praktyką jest najpierw uruchomienie konkretnej
klasy, np. Stack_char, a potem przekształcenie jej w
klasę ogólną - wzorzec Stack<T>.
Wzorce
• W wcześniejszej wersji wzorca wszystkie metody są inline —
zdefiniowano je wewnątrz deklaracji klasy. Można we wzorcu nie
definiować metod:
template <class T>
class Stack
{
T* v; // początek stosu
T* p; // koniec stosu
int size; // pojemność stosu
public:
Stack(int r); // deklaracja: konstruktor
~Stack(); // deklaracja: destruktor
void push(T a);// deklaracja: wstaw na stos
T pop(); // deklaracja: zdejmij ze stosu
int getSize(); // deklaracja: ile elementów jest na stosie
};
Wzorce
• Jeżeli metody wzorca definiujemy poza definicją klasy wzorca
to musimy użyć dla każdej z metod słowa kluczowego
template:
template<class T> // definicja metody wstaw
void Stack<T>::push(T a)
{
*p++ = a;
};

template<class T> // konstruktor


Stack<T>::Stack(int r)
{
v = p = new T[size = r];
};
Wzorce
• Przypomnienie: w zakresie deklaracji wzorca

template <class T> class Stack

używanie pełnej nazwy typu Stack<T> jest nadmiarowe zarówno


przy definicji metod wewnątrz klasy jak i poza nią.
– Wystarczy Stack zarówno dla określenia klasy jak i nazw konstruktora i
destruktora.
– Poniższy wzorzec jest błędny:

// template<class T>
// Stack<T>::Stack<T>(int r) // to jest błąd,
// // powinno być:
// // Stack<T>:: Stack(int,r)
//{
// v = p = new T[size=r];
//};
Rozbudowywanie klas-wzorców
• Wzorca który jest już napisany i wykorzystywany nie
należy modyfikować — modyfikacje te będą
dotyczyły wszystkich klas stworzonych w oparciu o
ten wzorzec.
– Gdy dodamy zmienne klasowe to powiększą się obiekty
wszystkich tych klas wywiedzionych ze wzorca.
– Gdy zmienimy definicje metod to zmiany będą dotyczyły
wsystkich klas wywiedzionych ze wzorca.

• Zatem, zamiast modyfikacji wzorca danej klasy należy


utworzyć wzorzec klasy pochodnej, o nowych
właściwościach.
Rozbudowywanie klas-wzorców
• Np.: potrzebujemy stosu łańcuchów z możliwością zapisu i odczytu
do pliku

template<class T>
class Filed_Stack : public Stack<T>
{
char * file_name;
public:

// konstruktor, parametry: rozmiar i nazwa pliku


Stack(int size, char * filename = NULL)
:Stack<T>(size) // konstrukcja rodzica
{ // tutajzachowaj nazwę pliku
}

void save_Stack();
void load_Stack();
};
Wzorzec szczegółowy
• Jeżeli wzorzec działa niepoprawnie dla jakiegoś szczególnego
parametru, to można zdefiniować inną wersję wzorca dla
konkretnego parametru. Np. klasa która służy do porównywania
elementów danego typu:
template<class T>
class comparator // wzorzec ogólny
{
public:
static int less(T &a, T &b)
{
return a<b;
}
};

• Powyższe jest poprawne dla typów takich, jak int czy char. Dla łańcuchów (char *)
porównywane by były nie łańcuchy, ale ich adresy,
Wzorzec szczegółowy
• Dla łańcuchów (char *) porównywane by były nie łańcuchy,
ale ich adresy, więc definiujemy szczególną postać wzorca
klasy porównywacz dla łańcuchów:
class comparator<char *> //wzorzec szczegółowy
{
public:
static int less(const char * a, const char * b)
{
return strcmp(a, b)<0;
}
};

• Kompilator wykorzysta szczególną postać wzorca, jeżeli w miejscu gdzie


będzie potrzebna, będzie widoczna (czyli zadeklarowana wcześniej). W
przeciwnym przypadku zostanie rozwinięty wzorzec ogólny.
Argumenty wzorca
• Argumentów wzorca może być wiele, oprócz klas i typów mogą to
być napisy, nazwy funkcji lub wyrażenia stałe.
• Np. wzorzec bufora, którego parametrem będzie rozmiar:

template<class T, int size>


class buffer
{
T w[size];
// ...
};

• taki wzorzec bufora wykorzystujemy np. następująco:

buffer<figure, 250> tbf; // deklaracja obiektu f będącego


// buforem na 250 figur
buffer<char, 100> tbc; // tbc - bufor na 100 znaków
Wzorce
• Dwa typy wygenerowane ze wspólnego wzorca są identyczne
jeżeli identyczne są argumenty wzorca, w przeciwnym
przypadku są różne i nie wiąże ich pokrewieństwo.

• Na przykład dla następujących deklaracji tylko obiekty tbc0 i


tbc1 należą do tej samej klasy (klasy buffer<char, 100>)
pozostałe obiekty do obiekty różnych klas.

buffer<char, 100> tbc0;


buffer<figure, 250> tbf0;
buffer<char, 100> tbc1;
buffer<figure, 300> tbf1;
Wzorce funkcji
template <class T> // jesteśmy poza
// deklaracją klasy
void exchange(T &x, T &y) // nie metoda, a funkcja
{
T t = x;
x = y;
y = t;
}

int a = 7, b = 8;
exchange(a, b); // kompilator rozwinie wzorzec
// (jeżeli jest widoczny)
Wzorce funkcji - przykład
• napisać rodzinę funkcji zwiększających wartość swojego pierwszego
argumentu aktualnego o wartość drugiego argumentu (oba to typy
liczbowe)
template <class t>
void increm(t &i, double d)// zadzaiala dla wszystkich typów
// liczbowych
{ // ale jak ktos wywola zwieksz(1, 1)
i += t(d); // to będą 2 automatyczne konwersje
}; // nieekologiczne - marnotrawstwo czasu

template <class t, class d>


void increm_optimised(t &i, const d delta) // const nie zaszkodzi
{ // a może się przyda
i += t(delta);
};
Wzorce funkcji - przykład
• napisać rodzinę funkcji zwiększających wartość swojego
pierwszego argumentu aktualnego o wartość drugiego
argumentu (oba to typy liczbowe), lub o 1 gdy nie podano
drugiego argumentu.

// template <class t, class d>


// void increm_1 (t &i, const d delta=1)
// …

• Pułapka: po napotkaniu wywołania


double dbl = 1.0;
increm_1(dbl);

• kompilator nie ma podstaw do określenia typu d!


Wzorce funkcji - przykład
• napisać rodzinę funkcji zwiększających wartość swojego pierwszego
argumentu aktualnego o wartość drugiego argumentu (oba to typy
liczbowe), lub o 1 gdy nie podano drugiego argumentu.

template <class t, class d>


void increm_1(t &i, const d delta)
{
i += t(delta);
};

template <class t>


void increm_1(t &i)
{
i += t(1);
};
Wzorce funkcji
• Alternatywna składnia deklaracji funkcji

template<typename T, typename U>


auto mul(T x, U y) -> decltype(x*y)
{
return x*y;
}
Template alias
• Definiowanie skróconej nazwy dla wzorców, zamiast typedef,
wygodniejsza składnia.
– Przykład za C++11 FAQ by Bjarne Stroustrup
http://www2.research.att.com/~bs/C++0xFAQ.html

template<class T>
using Vec = std::vector<T, My_alloc<T>>;
// standard vector using my allocator

Vec<int> fib = { 1, 2, 3, 5, 8, 13 };
// allocates elements using My_alloc

vector<int, My_alloc<int>> verbose = fib;


// verbose and fib are of the same type
Wzorce ze zmienną liczbą argumentów
(variadic templates)
• zagadnienie obszerne, przykład ponżej za: An Introduction to Variadic
Templates in C++0x by Anthony Williams
http://www.devx.com/cplus/Article/41533/0/page/1,
tam również szczegóły, różne sposoby dostępu do listy argumentów
template<typename T>
void print_comma_separated_list(T value)
{
std::cout << value << std::endl;
}

template<typename First, typename ... Rest>


void print_comma_separated_list(First first, Rest ... rest)
{
std::cout << first << ",";
print_comma_separated_list(rest...);
}
Wzorce
• Elementy języka (od C++11) użyteczne dla wzorców, lub przede wszystkim dla
wzorców

– extern template class std::vector<MyClass>;


- optymalizacja kompilacji: nie rozwijaj szablonu w tym obj, będzie rozwinięty w innym
module

– static_assert(condition, "Error message");


- asercje czasu kompilacji

– std::sort(indices.begin(), indices.end(),
[&](int a, int b) { return v[a].name<v[b].name; });
-funkcje lambda, omówimy je razem z algorytmami STL

– wzorzec initializer_list<T_elem> do obsługi ujednoliconej składni inicjalizacji


agregatów i zm. skalarnych, omówimy go razem z algorytmami STL

You might also like