r19.pdf

(328 KB) Pobierz
Szablon dla tlumaczy
Rozdział 19.
Wzorce
Przydatnym dla programistów C++ nowym narzędziem są „typy sparametryzowane”, czyli
wzorce . Są one tak użytecznym narzędziem, że do definicji języka C++ została wprowadzona
standardowa biblioteka wzorców (STL, Standard Template Library).
Z tego rozdziału dowiesz się:
czym są wzorce i jak ich używać,
jak tworzyć wzorce klas,
jak tworzyć wzorce funkcji,
czym jest standardowa biblioteka wzorców i jak z niej korzystać.
Czym są wzorce?
Pod koniec czternastego rozdziału stworzyliśmy obiekt PartsList i wykorzystaliśmy go do
stworzenia katalogu części, klasy PartsCatalog . Jeśli jednak chcielibyśmy na obiekcie
PartsList oprzeć listę kotów, pojawiłby się problem: klasa PartsList wie tylko o częściach.
Aby rozwiązać ten problem, moglibyśmy stworzyć klasę bazową List . Wtedy moglibyśmy
wyciąć i wkleić większość kodu z klasy PartsList do nowej deklaracji klasy CatsList .
Tydzień później, gdybyśmy zechcieli stworzyć listę obiektów Car , musielibyśmy stworzyć nową
klasę i znów kopiować i wklejać kod.
Nie jest to dobre rozwiązanie. Z czasem mogłoby się okazać, że klasa List oraz klasy z niej
wyprowadzone muszą być rozszerzone. Rozprowadzenie wszystkich zmian po wszystkich
powiązanych z nią klasach byłoby koszmarem.
Problem ten rozwiązują wzorce, które po zaadoptowaniu standardu ANSI stały się integralną
częścią języka. Podobnie jak wszystkie elementy w C++, są one bardzo elastyczne, a przy tym
bezpieczne (pod względem typów).
Typy parametryzowane
Wzorce pozwalają na nauczenie kompilatora, w jaki sposób ma tworzyć listę rzeczy dowolnego
typu (zamiast tworzenia zestawu list specyficznego typu) — PartsList jest listą części,
CatsList jest listą kotów. Listy te różnią się tylko rodzajem przechowywanych w nich rzeczy. W
trakcie korzystania z wzorców typ rzeczy zawartej w liście staje się parametrem definicji klasy.
Standardowym komponentem w prawie wszystkich bibliotekach C++ jest klasa tablicy. Jak
widzieliśmy w przypadku klasy List , tworzenie osobnych tablic dla liczb całkowitych, liczb
zmiennoprzecinkowych czy dla obiektów jest żmudne i nieefektywne. Wzorce umożliwiają
zadeklarowanie sparametryzowanej klasy tablicy, a następnie określenie, jaki typ obiektu będzie
zawierał każdy jej egzemplarz. Zauważ, że standardowa biblioteka wzorców zawiera standardowy
zestaw klas kontenerów , obejmujący tablice, listy i tak dalej. W tym rozdziale sami stworzymy
klasę kontenera, aby przekonać się, jak działają wzorce, ale w komercyjnych aplikacjach prawie z
pewnością użyjesz klas standardowych (zamiast tworzyć własne).
Tworzenie egzemplarza wzorca
Tworzenie egzemplarza wzorca oznacza tworzenie określonego typu na podstawie tego wzorca.
Poszczególne, tworzone aktualnie klasy są nazywane egzemplarzami wzorca.
Parametryzowane wzorce pozwalają na tworzenie klasy ogólnej i przekazywanie jej typów jako
parametrów, w celu stworzenia konkretnego egzemplarza wzorca.
Definicja wzorca
Parametryzowany obiekt Array (wzorzec dla tablic) deklarujemy, pisząc:
1: template <class T> // deklaruje wzorzec i parametr
2: class Array // parametryzowana klasa
3: {
4: public:
5: Array();
6: // w tym miejscu pełna deklaracja klasy
7: };
Słowo kluczowe template (wzorzec) jest używane na początku każdej deklaracji i definicji klasy
wzorcowej. Parametry wzorca występują po słowie kluczowym template . Parametry są
zmieniane (uaktualniane) w każdym egzemplarzu. Na przykład, w pokazanym tu wzorcu tablicy
zmienia się typ przechowywanych w niej obiektów. Jeden egzemplarz może być tablicą liczb
całkowitych, a inny może być tablicą obiektów typu Animal .
W tym przykładzie zostało użyte słowo kluczowe class , po którym następuje identyfikator T .
Słowo kluczowe class wskazuje że ten parametr (identyfikator) jest typem. Identyfikator T jest
używany w pozostałej części definicji wzorca i odnosi się do typu parametryzowanego. W jednym
z egzemplarzy tej klasy w miejscu wszystkich identyfikatorów T zostanie podstawiony na
przykład typ int , a w innym egzemplarzu zostanie podstawiony typ Cat .
Aby zadeklarować egzemplarze wzorca typu int i Cat parametryzowanej klasy Array ,
moglibyśmy napisać:
Array<int> anIntArray;
Array<Cat> aCatArray;
Obiekt anIntArray to obiekt typu tablica liczb całkowitych ; obiekt aCatArray to obiekt typu
tablica kotów . Od tego momentu możemy używać typu Array<int> wszędzie tam, gdzie
normalnie używalibyśmy jakiegoś typu (np. dla wartości zwracanej przez funkcję, jako parametru
funkcji i tak dalej). Pełną deklarację tej okrojonej klasy Array przedstawia listing 19.1.
UWAGA Listing 19.1 nie jest kompletnym programem!
Listing 19.1. Wzorzec klasy tablicy
0: //Listing 19.1 Wzorzec klasy tablicy
1: #include <iostream>
2: using namespace std;
3: const int DefaultSize = 10;
4:
5: template <class T> // deklaruje wzorzec i parametr
6: class Array // parametryzowana klasa
7: {
8: public:
9: // konstruktory
10: Array(int itsSize = DefaultSize);
11: Array(const Array &rhs);
12: ~Array() { delete [] pType; }
13:
14: // operatory
15: Array& operator=(const Array&);
16: T& operator[](int offSet) { return pType[offSet]; }
17:
18: // akcesory
19: int getSize() { return itsSize; }
20:
21: private:
22: T *pType;
23: int itsSize;
24: };
Wynik
Brak. Program nie jest kompletny.
Analiza
Definicja wzorca rozpoczyna się w linii 5. od słowa kluczowego template , po którym występuje
parametr. W tym przypadku parametr jest identyfikowany przez słowo kluczowe class , zaś do
reprezentowania parametryzowanego typu został użyty identyfikator T .
Od linii 6. aż do końca wzorca w linii 24., pozostała część deklaracji wygląda tak samo jak
deklaracja każdej innej klasy. Jedyną różnicą jest to, że tam, gdzie normalnie występowałby typ
obiektu, występuje identyfikator T . Na przykład, można oczekiwać, że operator[] będzie
zwracał referencję do obiektu zawartego w tablicy — rzeczywiście, jest zadeklarowany jako
zwracający referencję do T .
Gdy deklarowany jest egzemplarz tablicy liczb całkowitych, to zdefiniowany dla tej tablicy
operator= zwróci referencję do liczby całkowitej. Gdy jest deklarowany egzemplarz tablicy
obiektów klasy Animal , to zdefiniowany dla tej tablicy operator= zwróci referencję do obiektu
Animal .
Użycie nazwy
Słowo Array może być użyte bez kwalifikowania wewnątrz deklaracji klasy. We wszystkich
innych miejscach programu do klasy tej trzeba się odwoływać jako do Array<T> . Na przykład,
jeśli nie wpiszemy konstruktora wewnątrz deklaracji klasy, to musimy napisać:
template <class T>
Array<T>::Array(int size):
itsSize = size
{
pType = new T[size];
for (int i = 0; i < size; i++)
pType[i] = 0;
}
Deklaracja w pierwszej linii tego fragmentu kodu jest wymagana do zidentyfikowania typu
( class T ). Nazwą wzorca jest Array<T> , a nazwą funkcji jest Array(int size) . Pozostała
część funkcji jest taka sama, jak w przypadku zwykłej funkcji. Powszechną i zalecaną praktyką
jest przetestowanie działania klasy i jej funkcji jako zwykłych deklaracji przed zamienieniem ich
we wzorzec. To rozwiązanie upraszcza tworzenie wzorca, umożliwiając skoncentrowanie się na
celu programowania; rozwiązanie, poprzez stworzenie wzorca, uogólniamy dopiero później.
Implementowanie wzorca
Pełna implementacja klasy wzorca Array wymaga zaimplementowania konstruktora kopiującego,
operatora= i tak dalej. Prosty program sterujący, przenaczony do testowania tej klasy
wzorcowej przedstawia listing 19.2.
UWAGA Niektóre starsze kompilatory nie obsługują wzorców. Wzorce są jednak częścią
standardu ANSI C++ i są obsługiwane w obecnych wersjach kompilatorów wiodących
producentów. Jeśli masz bardzo stary kompilator, nie będziesz mógł skompilować i uruchomić
przykładowych programów przedstawionych w tym rozdziale. Jednak mimo to, powinieneś go w
całości i wrócić do niego później, gdy zdobędziesz nowszy kompilator.
Listing 19.2. Implementacja wzorca klasy tablicy
0: //Listing 19.2 Implementacja wzorca klasy tablicy
1: #include <iostream>
2:
3: const int DefaultSize = 10;
4:
5: // deklaruje prostą klasę Animal tak, abyśmy
6: // mogli tworzyć tablice obiektów typu Animal
7:
8: class Animal
9: {
10: public:
11: Animal(int);
12: Animal();
13: ~Animal() {}
14: int GetWeight() const { return itsWeight; }
15: void Display() const { std::cout << itsWeight; }
16: private:
17: int itsWeight;
18: };
19:
20: Animal::Animal(int weight):
21: itsWeight(weight)
22: {}
23:
24: Animal::Animal():
25: itsWeight(0)
26: {}
27:
28:
29: template <class T> // deklaruje wzorzec i parametr
30: class Array // parametryzowana klasa
31: {
32: public:
33: // konstruktory
34: Array(int itsSize = DefaultSize);
35: Array(const Array &rhs);
36: ~Array() { delete [] pType; }
37:
38: // operatory
39: Array& operator=(const Array&);
40: T& operator[](int offSet) { return pType[offSet]; }
Zgłoś jeśli naruszono regulamin