Zaawansowane CPP/Wykład 5: Klasy cech: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Matiunreal (dyskusja | edycje)
Pbialas (dyskusja | edycje)
 
(Nie pokazano 50 wersji utworzonych przez 7 użytkowników)
Linia 2: Linia 2:


Rozważmy próbę implementacji ogólnej funkcji sumowania elementów
Rozważmy próbę implementacji ogólnej funkcji sumowania elementów
tablicy (zob. <ref name="pierwszy">D. Vandervoorde, N. Josuttis "C++ Szablony, Vademecum profesjonalisty", Helion S.A 2003.</ref> rozd. 15).  Korzystając z wiadomości o
tablicy (zob. D. Vandervoorde, N. Josuttis <i>"C++ Szablony, Vademecum profesjonalisty"</i>, rozdz. 15).  Korzystając z wiadomości o
szablonach i konwencjach używanych w STL możemy napisać:
szablonach i konwencjach używanych w STL możemy napisać:


template<typename T> T sum(T *beg,T *end) {  
<Source>
  T total <nowiki>=</nowiki> T();  
template<typename T> T sum(T *beg,T *end) {  
  for(;beg!<nowiki>=</nowiki>end;++beg)
  T total = T();  
    total +<nowiki>=</nowiki> *beg;
  for(;beg!<nowiki>=</nowiki>end;++beg)
  }
    total +<nowiki>=</nowiki> *beg;
  return total;  
  return total;  
}  
}  
</Source>


[http://osilek.mimuw.edu.pl/images/7/7b/Sum1.cpp Źródło: Sum1.cpp]
([[media:Sum1.cpp | Źródło: sum1.cpp]])


{{przyklad|4.1||}}
{{kotwica|prz.5.1|}}{{przyklad|5.1||}}


Ten prosty kod ma jednak co najmniej dwa problemy. Pierwszy związany
Ten prosty kod ma jednak co najmniej dwa problemy. Pierwszy związany
jest z linijką
jest z linijką


T total <nowiki>=</nowiki> T();
<Source>
 
T total <nowiki>=</nowiki> T();
</Source>
i wiąże się z ustaleniem zerowej wartości dla danego typu.  Powyższa
i wiąże się z ustaleniem zerowej wartości dla danego typu.  Powyższa
linijka oznacza że zmienna <tt>total</tt> jest inicjalizowana
linijka oznacza, że zmienna <tt>total</tt> jest inicjalizowana
konstruktorem domyślnym klasy <tt>T</tt>. W przypadku typów wbudowanych
konstruktorem domyślnym klasy <tt>T</tt>. W przypadku typów wbudowanych
będzie to inicjalizacja wartością zerową, czyli tak jak tego
będzie to inicjalizacja wartością zerową, czyli tak jak tego
oczekujemy.  W przypadku innych typów możemy mieć tylko nadzieję że
oczekujemy.  W przypadku innych typów możemy mieć tylko nadzieję, że
konstruktor defaultowy istnieje i robi to co trzeba :) Popatrzmy na
konstruktor defaultowy istnieje i robi to co trzeba :). Popatrzmy na
możliwe alternatywy:
możliwe alternatywy:


T total;
<Source>
 
T total;
W przypadku typów zdefiniowanych przez użytkownika, wywoływany jest
</Source>
W przypadku typów zdefiniowanych przez użytkownika wywoływany jest
defaultowy konstruktor (domyślny jeśli żaden inny nie jest zdefinowany).  
defaultowy konstruktor (domyślny jeśli żaden inny nie jest zdefinowany).  
W przypadku typów wbudowanych wartość jest niekreślona!
W przypadku typów wbudowanych wartość jest niekreślona!


T total <nowiki>=</nowiki> 0;
<Source>
 
T total <nowiki>=</nowiki> 0;
jest z kolei niepoprawne dla typów na które nie ma rzutowania z liczb
</Source>
jest z kolei niepoprawne dla typów, na które nie ma rzutowania z liczb
całkowitych.
całkowitych.


Problem można ominąć jeżeli się zauważy że dla niepustych zakresów
Problem można ominąć jeżeli się zauważy, że dla niepustych zakresów
tzn. <tt>beg<nowiki>=</nowiki>end</tt> nie potrzebujemy wcale wartości zerowej:
tzn. <tt>beg<nowiki>!=</nowiki>end</tt> nie potrzebujemy wcale wartości zerowej:


template<typename T> T sum(T *beg,T *end) {  
<Source>
  T total<nowiki>=</nowiki> *beg;
template<typename T> T sum(T *beg,T *end) {  
  ++beg;
  T total= *beg;
  while(beg !<nowiki>=</nowiki> end ) {  
  ++beg;
     total +<nowiki>=</nowiki> *beg; beg++;  
  while(beg != end ) {  
}  
     total += *beg; beg++;  
return total;  
  }  
}  
  return total;  
}  
</Source>


Jeśli jednak dopuszczamy podanie zakresu pustego, to funkcja powinna
Jeśli jednak dopuszczamy podanie zakresu pustego, to funkcja powinna
zwrócić zero i problem powraca.  
zwrócić zero i problem powraca.  


Drugi problem przykładem <font color=red>{[##ex:sum1|Uzupelnic ex:sum1|]}</font> to typ zmiennej <tt>total</tt>.  
Drugi problem z przykładem [[#prz.5.1|5.1]] to typ zmiennej <tt>total</tt>.  
Popatrzmy na zastosowanie funkcji <tt>sum</tt>.  
Popatrzmy na zastosowanie funkcji <tt>sum</tt>.  


char name[]<nowiki>=</nowiki>"@ @ @";
<Source>
int length<nowiki>=</nowiki>strlen(name);<br>
char name[]="@ @ @";
cout<<sum(name,&name[length]);
int length=strlen(name);
 
cout<<sum(name,&name[length]]);
</Source>
Programik powinien wypisać sumę wartości znaków w napisie
Programik powinien wypisać sumę wartości znaków w napisie
<tt>"szablony"</tt>. Łatwo sprawdzić że wypisuje zero. Problem polega
<tt>"name"</tt>. Łatwo sprawdzić że wypisuje znak o kodzie zero. Problem polega
na tym że typ <tt>T</tt> niekoniecznie musi pomieścić wynik dodawania
na tym, że typ <tt>T</tt> niekoniecznie musi pomieścić wynik dodawania
elementów typu <tt>T</tt>. W tym przykładzie dodawanie znaków dało wynik
elementów typu <tt>T</tt>. W tym przykładzie dodawanie znaków dało wynik
256 (co za niezwykły zbieg okoliczności) który już nie mieści się w
256 (co za niezwykły zbieg okoliczności), który już nie mieści się w
zakresie tego typu.
zakresie tego typu.


Prostym rozwiązaniem jest dodanie dodatkowego parametru szablonu:
Prostym rozwiązaniem jest dodanie dodatkowego parametru szablonu:


template<typename R,typename T> R sum(T *beg,T *end) {  
<Source>
  R total <nowiki>=</nowiki> R();  
template<typename R,typename T> R sum(T *beg,T *end) {  
  while(beg !<nowiki>=</nowiki> end ) {  
  R total = R();  
    total +<nowiki>=</nowiki> *beg; beg++;  
  while(beg != end ) {  
  }  
    total += *beg; beg++;  
  return total;  
  }  
}
  return total;  
 
}
[http://osilek.mimuw.edu.pl/images/a/a4/Sum2.cpp Źródło: Sum2.cpp]
</Source>
([[media:Sum2.cpp | Źródło: sum2.cpp]])


i wtedy zastosowanie
i wtedy zastosowanie
 
<Source>
cout<<sum<int>(name,&name[length])<<endl;
cout<<sum<int>(name,&name[length]])<<endl;
 
</Source>
da już oczekiwany wynik. Zaletą tego rozwiązania jest jego prostota i
da już oczekiwany wynik. Zaletą tego rozwiązania jest jego prostota i
duża elastyczność. Wadą zwiększenie liczby parametrów szablonu, co
duża elastyczność. Wadą - zwiększenie liczby parametrów szablonu, co
zawsze zwiększa złożoność kodu i możliwości popełnienia błedu.
zawsze zwiększa złożoność kodu i możliwości popełnienia błędu, zwłaszcza, że typ <tt>R</tt> jest w większości przypadków określony przez
Zwłaszcza że typ <tt>R</tt> jest w większości przypadków określony przez
typ <tt>T</tt> i nie wnosi niezależnej informacji.
typ <tt>T</tt> i nie wnosi niezależnej informacji.


==Klasy cech==
==Klasy cech==


Pomocą mogą służyć klasy cech: klasy których funkcją jest dostarczanie
Pomocą mogą służyć klasy cech: klasy, których funkcją jest dostarczanie
dodatkowych informacji o danym typie. W naszym przypadku możemy
dodatkowych informacji o danym typie. W naszym przypadku możemy
zadeklarować szablon:
zadeklarować szablon:
Linia 118: Linia 125:


  template<typename T>  
  template<typename T>  
  typename sum_traits<T>::sum_type sum(T *beg,T *end) {  
typename sum_traits<T>::sum_type sum(T *beg,T *end) {  
   typedef typename sum_traits<T>::sum_type sum_type;
   typedef typename sum_traits<T>::sum_type sum_type;
   sum_type total <nowiki>=</nowiki> sum_type();  
   sum_type total <nowiki>=</nowiki> sum_type();  
Linia 127: Linia 134:
  }
  }


[http://osilek.mimuw.edu.pl/images/5/55/Sum3.cpp Źródło: Sum3.cpp]
([[media:Sum3.cpp | Źródło: sum3.cpp]])


Wadą tego podejścia jest konieczność definiowanie specjalizacji
Wadą tego podejścia jest konieczność definiowania specjalizacji
szablony <tt>sum_traits</tt> dla każdego typu którego sumę będziemy
szablonu <tt>sum_traits</tt> dla każdego typu, którego sumę będziemy
chcieli obliczyć. Można tego uniknąć definiując szablon ogólny
chcieli obliczyć. Można tego uniknąć definiując szablon ogólny


Linia 144: Linia 151:
bez dodatkowych definicji. To czy należy implementować uniwersalną
bez dodatkowych definicji. To czy należy implementować uniwersalną
definicję klasy cech zależy od tego czy istnieje sensowna wartość
definicję klasy cech zależy od tego czy istnieje sensowna wartość
domyślna dla danej cechy. W naszym przypadku definiując powyższy szablon
domyślna dla danej cechy. W naszym przypadku, definiując powyższy szablon,
zyskujemy na wygodzie, ale tracimy na bezpieczeństwie bo łatwiej jest teraz
zyskujemy na wygodzie ale tracimy na bezpieczeństwie, bo łatwiej jest teraz
wywołać funkcję <tt>sum</tt> z nieodpowiednim typem zmiennej <tt>total</tt>.
wywołać funkcję <tt>sum</tt> z nieodpowiednim typem zmiennej <tt>total</tt>.


Linia 152: Linia 159:
Możemy spróbować rozwiązać za pomocą klas cech również problem
Możemy spróbować rozwiązać za pomocą klas cech również problem
inicjalizacji zmiennej <tt>total</tt>, definiując w każdej klasie
inicjalizacji zmiennej <tt>total</tt>, definiując w każdej klasie
odpwiednią wartość zerową dla danego typu. Pytanie jak to zrobic?
odpwiednią wartość zerową dla danego typu. Pytanie jak to zrobić?
Nasuwa się użycie stałych składowych statycznych:
Nasuwa się użycie stałych składowych statycznych:


Linia 160: Linia 167:
  };
  };


Sęk w tym że w standard zezwala na incijalizowanie w klasie
Sęk w tym, że standard zezwala na incijalizowanie w klasie
statycznych stałych jedynie dla typów całkowitoliczbowych. Taka sama
statycznych stałych jedynie dla typów całkowitoliczbowych. Taka sama
konstrucja dla <tt>double</tt> już nie jest możliwa.
konstrucja dla <tt>double</tt> już nie jest możliwa.
Linia 166: Linia 173:
  template<>  struct sum_traits<float> {
  template<>  struct sum_traits<float> {
  typedef double  sum_type;
  typedef double  sum_type;
  static  const sum_type zero <nowiki>=</nowiki> 0.0; //<-- niedozwolone -->//
  static  const sum_type zero <nowiki>=</nowiki> 0.0; <i>niedozwolone</i>
  };
  };


Linia 174: Linia 181:
  sum_traits<float>::zero <nowiki>=</nowiki> 0.0;  
  sum_traits<float>::zero <nowiki>=</nowiki> 0.0;  


musi być umieszczony w kodzie źródłówym. Popierwsze nie bardzo wiadomo
musi być umieszczony w kodzie źródłówym. Po pierwsze nie bardzo wiadomo
gdzie go umieśić (nie może być w pliku nagłówkowym bo łamało by to
gdzie go umieścić (nie może być w pliku nagłówkowym, bo łamało by to
zasadę jednokrotnej definicji). Po drugie kompilator najprawdopodobniej
zasadę jednokrotnej definicji). Po drugie kompilator najprawdopodobniej
nie umiałby powiązać nazwy stałej i jej wartości w czasie kompilacji.  
nie umiałby powiązać nazwy stałej i jej wartości w czasie kompilacji.  


Inną możliwośćią jest użycie funkcji statycznych rozwijanych w miejscu
Inną możliwością jest użycie funkcji statycznych rozwijanych w miejscu
wywołania:
wywołania:


Linia 200: Linia 207:
     total +<nowiki>=</nowiki> *beg; beg++;  
     total +<nowiki>=</nowiki> *beg; beg++;  
   }  
   }  
return total;  
  return total;  
  }
  }


Linia 209: Linia 216:


Opisana powyżej implementacja funkcji <tt>sum</tt> i związanej z nią klasy
Opisana powyżej implementacja funkcji <tt>sum</tt> i związanej z nią klasy
<tt>sum_traits</tt> jest mało elastycza. Wybierając typ przekazanej
<tt>sum_traits</tt> jest mało elastyczna. Wybierając typ przekazanej
tablicy wybieramy typ zmiennej <tt>total</tt>. Może się jednak zdażyć że
tablicy wybieramy typ zmiennej <tt>total</tt>. Może się jednak zdażyć, że
chcemy sumować <tt>int</tt> we <tt>float</tt>, a <tt>float</tt> we <tt>float</tt>.  
chcemy sumować <tt>int</tt> we <tt>float</tt>, a <tt>float</tt> we <tt>float</tt>.  


Możemy dodać dodatkowy paremetr do szablonu który bedzie definiował
Możemy dodać dodatkowy parametr do szablonu, który będzie definiował
wybraną klasę cech. Ale to jest powrót do rozwiązania odrzuconegona
wybraną klasę cech. Ale to jest powrót do rozwiązania odrzuconego na
początku. Rozwiązaniem może być uczynienie tego parametru, paramtrem
początku. Rozwiązaniem może być uczynienie tego parametru parametrem
domyślnym tak aby nie trzeba było podawać go jawnie w typowych
domyślnym, tak, aby nie trzeba było podawać go jawnie w typowych
przypadkach. Jest to bardzo dobre rozwiązanie w przypadku użycia klas
przypadkach. Jest to bardzo dobre rozwiązanie w przypadku użycia klas
cech w szablonach klas. Problem w tym że szablony funkcji nie
cech w szablonach klas. Problem w tym, że szablony funkcji nie
dopuszczaja stosowania parametrów domyślnych.
dopuszczają stosowania parametrów domyślnych.
Możemy to obejść za pomocą przeciążenia definiując:
Możemy to obejść za pomocą przeciążenia definiując:


Linia 239: Linia 246:
  };
  };


[http://osilek.mimuw.edu.pl/images/9/98/Sum4.cpp Źródło: Sum4.cpp]
([[media:Sum4.cpp | Źródło: sum4.cpp]])


  main() {
  main() {
  char name[]<nowiki>=</nowiki>"@ @ @";
  char name[]<nowiki>=</nowiki>"@ @ @";
  int length<nowiki>=</nowiki>strlen(name);<br>
  int length<nowiki>=</nowiki>strlen(name);<br>
   cout<<(int)sum(name,&name[length])<<endl;
   cout<<(int)sum(name,&name[length]])<<endl;
   cout<<(int)sum<char_sum>(name,&name[length])<<endl;
   cout<<(int)sum<char_sum>(name,&name[length]])<<endl;
   cout<<(int)sum<char>(name,&name[length])<<endl;
   cout<<(int)sum<char>(name,&name[length]])<<endl;
  }
  }


[http://osilek.mimuw.edu.pl/images/9/98/Sum4.cpp Źródło: Sum4.cpp]
([[media:Sum4.cpp | Źródło: sum4.cpp]])


==iteratortraits==
==iterator_traits==


Na koniec spróbujmy uogólnić funkcję <tt>sum</tt> aby działała nie tylko
Na koniec spróbujmy uogólnić funkcję <tt>sum</tt>, aby działała nie tylko
ze wskaźnikami ale i iteratorami.  
ze wskaźnikami, ale i iteratorami.  


  template<typename IT> sum(IT *beg,IT *end);  
  template<typename IT> sum(IT *beg,IT *end);  


Widać że tu użycie klas cech jest już niezbędne, musimy bowiem
Widać, że tu użycie klas cech jest już niezbędne, musimy bowiem
dowiedzieć się na obiekty jakiego typu wskazuje iterator. Nie można do
dowiedzieć się na obiekty jakiego typu wskazuje iterator. Nie można do
tego celu użyć typów stowarzyszonych <tt>IT::value_type</tt> bo jako
tego celu użyć typów stowarzyszonych <tt>IT::value_type</tt>, bo jako
iterator może zostać podstawiony zwykły wskaźnik.  Dlatego w STL
iterator może zostać podstawiony zwykły wskaźnik.  Dlatego w STL
istnieje klasa <tt>iterator_traits</tt> definiująca podstawowe typu dla
istnieje klasa <tt>iterator_traits</tt>, definiująca podstawowe typy dla
każdego rodzaju iteratora. Korzystając z tej klasy można zdefiniować
każdego rodzaju iteratora. Korzystając z tej klasy można zdefiniować
ogólny szalon funkcji <tt>sum</tt>
ogólny szalon funkcji <tt>sum</tt>
Linia 290: Linia 297:
     }
     }


{{przyklad|4.2||}}
{{kotwica|prz.5.2|}}{{przyklad|5.2||}}


Dodanie dodatkowego parametru wywołania funkcji rozwiązuje za jednym
Dodanie dodatkowego parametru wywołania funkcji rozwiązuje za jednym
zamachem oba nasze problemy: parametr ten dostarcza zarówno typu
zamachem oba nasze problemy: parametr ten dostarcza zarówno typu,
jak i wartości początkowej dla zmiennej sumującej.  Są jednak inne
jak i wartości początkowej dla zmiennej sumującej.  Są jednak inne
algorytmy w STL które wymagają więcej informacji o iteratorzez i muszą
algorytmy w STL, które wymagają więcej informacji o iteratorze i muszą
je pobrać za pomocą <tt>iterator_traits</tt>.  
je pobrać za pomocą <tt>iterator_traits</tt>.  


Dla iteratorów nie bedących wskaźnikami <tt>iterator_traits</tt> po prosty
Dla iteratorów nie będących wskaźnikami <tt>iterator_traits</tt> po prostu
przepisuja ich typy stowarzyszone:  
przepisują ich typy stowarzyszone:  


  template <class Iterator>
  template <class Iterator>
Linia 321: Linia 328:
   };
   };


Widać więc że każdy iterator nie będący wskaźnikiem musi mieć
Widać więc, że każdy iterator nie będący wskaźnikiem musi mieć
zdefiniowane odpowiednie typy stowarzyszone. Ułatwia to szablon klasy
zdefiniowane odpowiednie typy stowarzyszone. Ułatwia to szablon klasy
<tt>iterator</tt> z którego można dziedziczyć:
<tt>iterator</tt>, z którego można dziedziczyć:


  namespace std {
  namespace std {
Linia 339: Linia 346:
automatycznego wyboru odpowiednich funkcji w oparciu o kategorię
automatycznego wyboru odpowiednich funkcji w oparciu o kategorię
iteratora. Kategorie odpowiadają konceptom iteratorów i są
iteratora. Kategorie odpowiadają konceptom iteratorów i są
reprezentowane przez puste klasy. W STL predefiniowane jest pięć kategorii   
reprezentowane przez puste klasy. W STL zdefiniowano pięć kategorii   
iteratorów:
iteratorów:


Linia 350: Linia 357:
  }
  }


Aby zilustrować zastosowanie typy <tt>iterator_category</tt> przedstawię
Aby zilustrować zastosowanie typu <tt>iterator_category</tt> przedstawię
implementację funkcji <tt>distance()</tt> która oblicza odległość pomiędzy
implementację funkcji <tt>distance()</tt>, która oblicza odległość pomiędzy
dwoma iteratorami. Potrzeba użycia <tt>iterator_category</tt> bierze się
dwoma iteratorami. Potrzeba użycia <tt>iterator_category</tt> bierze się
stąd, że dla iteratorów o dostępie swobodnym możemy policzyć ją
stąd, że dla iteratorów o dostępie swobodnym możemy policzyć ją
bezpośrednio porzez odejmowanie:  
bezpośrednio przez odejmowanie:  


  template <class _RandomAccessIterator>
  template <class _RandomAccessIterator>
Linia 361: Linia 368:
  __distance(_RandomAccessIterator __first,  
  __distance(_RandomAccessIterator __first,  
             _RandomAccessIterator __last,
             _RandomAccessIterator __last,
               random_access_iterator_tag) {
               random_access_iterator_tag) {<br>
 
   return __last - __first;
   return __last - __first;
  }
  }
Linia 383: Linia 389:
  }
  }


Do wybory pomiędzy tymi dwoma
Do wyboru pomiędzy tymi dwoma
implementacjami służy właśnie <tt>iterator_category</tt>:
implementacjami służy właśnie <tt>iterator_category</tt>:


Linia 394: Linia 400:
       iterator_traits<_InputIterator>::iterator_category  
       iterator_traits<_InputIterator>::iterator_category  
     _Category;<br>
     _Category;<br>
   return __distance(__first, __last, _Category());
   return __distance(__first, __last, _Category());
  }
  }


==numericlimits==
==numeric_limits==
 
Przykładem klasy cech zawartej w standardzie C++ jest szablon
<tt>numeric_limits</tt>, który zastępuje używane w C makra, zdefiniowane
w pliku <tt>limits.h</tt>. Szablon <tt>numeric_limits</tt> posiada specjalizację dla
każdego typu podstawowego wbudowanego (zob. tab. [[#tab.5.1|5.1]]]) i zawiera informację na temat różnych cech ich implementacji (zob. tab. [[#tab.5.2|5.2]]]).
Warto zwrócić uwagę na następującą konstrukcję: szablon
<tt>numeric_limits</tt> definiuje stałą logiczną <tt>is_specialised</tt>.
Domyślnie jest ona równa <tt>false</tt>. Każda specjalizacja szablonu
ustawia ją na <tt>true</tt>. W ten sposób stała
<tt>std::numeric_limits<T>::is_specialised</tt> mówi nam czy dany typ jest
opisany przez <tt>numeric_limits</tt> czy nie.


Poza <tt>iterator_traits</tt> w C++ są jeszcze zdefiniowane dwie inne
{{kotwica|tab.5.1|}}
klasy cech: <tt>chair_traits</ttktóre omówimy w następnym rozdziale i
namespace std {
<tt>numeric_limits</tt>
template<class T> class numeric_limits;
enum float_round_style;
enum float_denorm_style;
template<> class numeric_limits<bool>;
template<> class numeric_limits<char>;
template<> class numeric_limits<signed char>;
template<> class numeric_limits<unsigned char>;
template<> class numeric_limits<wchar_t>;
  template<> class numeric_limits<short>;
template<> class numeric_limits<int>;
template<> class numeric_limits<long>;
template<> class numeric_limits<unsigned short>;
template<> class numeric_limits<unsigned int>;
template<> class numeric_limits<unsigned long>;
template<> class numeric_limits<float>;
template<> class numeric_limits<double>;
template<> class numeric_limits<long double>;
}


==chartraits==
Tablica 5.1. Specjalizacje szablonu <tt>numeric_limits</tt>


==Podsumowanie==
{{kotwica|tab.5.2|}}
namespace std {
template<class T> class numeric_limits {
public:
static const bool is_specialized <nowiki>=</nowiki> false;
static T min() throw();
static T max() throw();
static const int digits <nowiki>=</nowiki> 0;
static const int digits10 <nowiki>=</nowiki> 0;
static const bool is_signed <nowiki>=</nowiki> false;
static const bool is_integer <nowiki>=</nowiki> false;
static const bool is_exact <nowiki>=</nowiki> false;
static const int radix <nowiki>=</nowiki> 0;
static T epsilon() throw();
static T round_error() throw();
static const int min_exponent <nowiki>=</nowiki> 0;
static const int min_exponent10 <nowiki>=</nowiki> 0;
static const int max_exponent <nowiki>=</nowiki> 0;
static const int max_exponent10 <nowiki>=</nowiki> 0;
static const bool has_infinity <nowiki>=</nowiki> false;
static const bool has_quiet_NaN <nowiki>=</nowiki> false;
static const bool has_signaling_NaN <nowiki>=</nowiki> false;
static const float_denorm_style has_denorm <nowiki>=</nowiki> denorm_absent;
static const bool has_denorm_loss <nowiki>=</nowiki> false;
static T infinity() throw();
static T quiet_NaN() throw();
static T signaling_NaN() throw();
static T denorm_min() throw();
static const bool is_iec559 <nowiki>=</nowiki> false;
static const bool is_bounded <nowiki>=</nowiki> false;
static const bool is_modulo <nowiki>=</nowiki> false;
static const bool traps <nowiki>=</nowiki> false;
static const bool tinyness_before <nowiki>=</nowiki> false;
static const float_round_style round_style <nowiki>=</nowiki> round_toward_zero;
};
}


==Przypisy==
Tablica 5.2. Szablon <tt>numeric_limits</tt>
<references/>

Aktualna wersja na dzień 11:39, 22 lis 2006

Wprowadzenie

Rozważmy próbę implementacji ogólnej funkcji sumowania elementów tablicy (zob. D. Vandervoorde, N. Josuttis "C++ Szablony, Vademecum profesjonalisty", rozdz. 15). Korzystając z wiadomości o szablonach i konwencjach używanych w STL możemy napisać:

template<typename T> T sum(T *beg,T *end) { 
  T total = T(); 
  for(;beg!<nowiki>=</nowiki>end;++beg)
    total +<nowiki>=</nowiki> *beg;
  return total; 
}

( Źródło: sum1.cpp)

Przykład 5.1

Ten prosty kod ma jednak co najmniej dwa problemy. Pierwszy związany jest z linijką

T total <nowiki>=</nowiki> T();

i wiąże się z ustaleniem zerowej wartości dla danego typu. Powyższa linijka oznacza, że zmienna total jest inicjalizowana konstruktorem domyślnym klasy T. W przypadku typów wbudowanych będzie to inicjalizacja wartością zerową, czyli tak jak tego oczekujemy. W przypadku innych typów możemy mieć tylko nadzieję, że konstruktor defaultowy istnieje i robi to co trzeba :). Popatrzmy na możliwe alternatywy:

T total;

W przypadku typów zdefiniowanych przez użytkownika wywoływany jest defaultowy konstruktor (domyślny jeśli żaden inny nie jest zdefinowany). W przypadku typów wbudowanych wartość jest niekreślona!

T total <nowiki>=</nowiki> 0;

jest z kolei niepoprawne dla typów, na które nie ma rzutowania z liczb całkowitych.

Problem można ominąć jeżeli się zauważy, że dla niepustych zakresów tzn. beg!=end nie potrzebujemy wcale wartości zerowej:

template<typename T> T sum(T *beg,T *end) { 
  T total= *beg;
  ++beg;
  while(beg != end ) { 
     total += *beg; beg++; 
  } 
  return total; 
}

Jeśli jednak dopuszczamy podanie zakresu pustego, to funkcja powinna zwrócić zero i problem powraca.

Drugi problem z przykładem 5.1 to typ zmiennej total. Popatrzmy na zastosowanie funkcji sum.

char name[]="@ @ @";
int length=strlen(name);
cout<<sum(name,&name[length]]);

Programik powinien wypisać sumę wartości znaków w napisie "name". Łatwo sprawdzić że wypisuje znak o kodzie zero. Problem polega na tym, że typ T niekoniecznie musi pomieścić wynik dodawania elementów typu T. W tym przykładzie dodawanie znaków dało wynik 256 (co za niezwykły zbieg okoliczności), który już nie mieści się w zakresie tego typu.

Prostym rozwiązaniem jest dodanie dodatkowego parametru szablonu:

template<typename R,typename T> R sum(T *beg,T *end) { 
  R total = R(); 
  while(beg != end ) { 
    total += *beg; beg++; 
  } 
  return total; 
}

( Źródło: sum2.cpp)

i wtedy zastosowanie

cout<<sum<int>(name,&name[length]])<<endl;

da już oczekiwany wynik. Zaletą tego rozwiązania jest jego prostota i duża elastyczność. Wadą - zwiększenie liczby parametrów szablonu, co zawsze zwiększa złożoność kodu i możliwości popełnienia błędu, zwłaszcza, że typ R jest w większości przypadków określony przez typ T i nie wnosi niezależnej informacji.

Klasy cech

Pomocą mogą służyć klasy cech: klasy, których funkcją jest dostarczanie dodatkowych informacji o danym typie. W naszym przypadku możemy zadeklarować szablon:

template<typename T>  struct sum_traits;

i jego specjalizacje:

template<>  struct sum_traits<char> {
typedef int sum_type; 
};
template<>  struct sum_traits<int> {
typedef long int sum_type; 
};
template<>  struct sum_traits<float> {
typedef double sum_type; 
};
template<>  struct sum_traits<double> {
typedef double sum_type; 
};

Szablon sum przerabiamy teraz na

template<typename T> 
typename sum_traits<T>::sum_type sum(T *beg,T *end) { 
  typedef typename sum_traits<T>::sum_type sum_type;
  sum_type total = sum_type(); 
  while(beg != end ) { 
    total += *beg; beg++; 
  } 
  return total; 
}

( Źródło: sum3.cpp)

Wadą tego podejścia jest konieczność definiowania specjalizacji szablonu sum_traits dla każdego typu, którego sumę będziemy chcieli obliczyć. Można tego uniknąć definiując szablon ogólny

template<typename T>  struct sum_traits {
typedef T sum_type;
};

i możemy już wtedy użyć

complex<double> *c1,*c2;
sum(c1,c2);

bez dodatkowych definicji. To czy należy implementować uniwersalną definicję klasy cech zależy od tego czy istnieje sensowna wartość domyślna dla danej cechy. W naszym przypadku, definiując powyższy szablon, zyskujemy na wygodzie ale tracimy na bezpieczeństwie, bo łatwiej jest teraz wywołać funkcję sum z nieodpowiednim typem zmiennej total.

Cechy wartości

Możemy spróbować rozwiązać za pomocą klas cech również problem inicjalizacji zmiennej total, definiując w każdej klasie odpwiednią wartość zerową dla danego typu. Pytanie jak to zrobić? Nasuwa się użycie stałych składowych statycznych:

template<>  struct sum_traits<char> {
typedef int  sum_type;
static const sum_type zero = 0; 
};

Sęk w tym, że standard zezwala na incijalizowanie w klasie statycznych stałych jedynie dla typów całkowitoliczbowych. Taka sama konstrucja dla double już nie jest możliwa.

template<>  struct sum_traits<float> {
typedef double  sum_type;
static  const sum_type zero = 0.0; niedozwolone
};

Inicjalizator

const typename sum_traits<float>::sum_type 
sum_traits<float>::zero = 0.0; 

musi być umieszczony w kodzie źródłówym. Po pierwsze nie bardzo wiadomo gdzie go umieścić (nie może być w pliku nagłówkowym, bo łamało by to zasadę jednokrotnej definicji). Po drugie kompilator najprawdopodobniej nie umiałby powiązać nazwy stałej i jej wartości w czasie kompilacji.

Inną możliwością jest użycie funkcji statycznych rozwijanych w miejscu wywołania:

template<>  struct sum_traits<char> {
typedef int  sum_type;
static  sum_type zero() {return 0;} 
};
template<>  struct sum_traits<float> {
typedef double  sum_type;
static  sum_type zero() {return 0.0;}
};

Odpowiadający temu podejściu kod funkcji sum bedzie wyglądał następująco:

template<typename T> 
typename sum_traits<T>::sum_type sum(T *beg,T *end) { 
  typedef typename sum_traits<T>::sum_type sum_type;
  sum_type total = sum_traits<T>::zero(); 
  while(beg != end ) { 
    total += *beg; beg++; 
  } 
  return total; 
}

Dobry kompilator powinien bez trudu rozwinąć definicję funkcji i podstawić odpowiednią wartość bezpośrednio w kodzie.

Parametryzacja klasami cech

Opisana powyżej implementacja funkcji sum i związanej z nią klasy sum_traits jest mało elastyczna. Wybierając typ przekazanej tablicy wybieramy typ zmiennej total. Może się jednak zdażyć, że chcemy sumować int we float, a float we float.

Możemy dodać dodatkowy parametr do szablonu, który będzie definiował wybraną klasę cech. Ale to jest powrót do rozwiązania odrzuconego na początku. Rozwiązaniem może być uczynienie tego parametru parametrem domyślnym, tak, aby nie trzeba było podawać go jawnie w typowych przypadkach. Jest to bardzo dobre rozwiązanie w przypadku użycia klas cech w szablonach klas. Problem w tym, że szablony funkcji nie dopuszczają stosowania parametrów domyślnych. Możemy to obejść za pomocą przeciążenia definiując:

template<typename Traits,typename T > 
typename Traits::sum_type sum(T *beg,T *end) { 
  typedef typename Traits::sum_type sum_type;
  sum_type total = sum_type(); 
  while(beg != end ) { 
    total += *beg; beg++; 
  } 
  return total; 
};
template<typename T > typename sum_traits<T>::sum_type sum(T *beg,T *end) { return sum<sum_traits<T>, T>(beg,end); }
struct char_sum { typedef char sum_type; };

( Źródło: sum4.cpp)

main() {
char name[]="@ @ @";
int length=strlen(name);
cout<<(int)sum(name,&name[length]])<<endl; cout<<(int)sum<char_sum>(name,&name[length]])<<endl; cout<<(int)sum<char>(name,&name[length]])<<endl; }

( Źródło: sum4.cpp)

iterator_traits

Na koniec spróbujmy uogólnić funkcję sum, aby działała nie tylko ze wskaźnikami, ale i iteratorami.

template<typename IT> sum(IT *beg,IT *end); 

Widać, że tu użycie klas cech jest już niezbędne, musimy bowiem dowiedzieć się na obiekty jakiego typu wskazuje iterator. Nie można do tego celu użyć typów stowarzyszonych IT::value_type, bo jako iterator może zostać podstawiony zwykły wskaźnik. Dlatego w STL istnieje klasa iterator_traits, definiująca podstawowe typy dla każdego rodzaju iteratora. Korzystając z tej klasy można zdefiniować ogólny szalon funkcji sum

template<typename II> 
typename 
sum_traits<typename iterator_traits<II>::value_type>::sum_type 
sum(II beg,II *end) { 
  typedef typename iterator_traits<IT>::value_type value_type;
  typedef typename sum_traits<value_type>::sum_type sum_type;
  sum_type total = sum_traits<value_type>::zero(); 
  while(beg != end ) { 
    total += *beg; beg++; 
  } 
  return total; 
}

Zanim omówię klasę iterator_trais podam rozwiązanie zastosowane w STL. Tam funkcja nazywa się accumulate i jest zaimplementowana następująco:

template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init) {
      for (; first != last; ++first)
        init = init + *first;
      return init;
    }

Przykład 5.2

Dodanie dodatkowego parametru wywołania funkcji rozwiązuje za jednym zamachem oba nasze problemy: parametr ten dostarcza zarówno typu, jak i wartości początkowej dla zmiennej sumującej. Są jednak inne algorytmy w STL, które wymagają więcej informacji o iteratorze i muszą je pobrać za pomocą iterator_traits.

Dla iteratorów nie będących wskaźnikami iterator_traits po prostu przepisują ich typy stowarzyszone:

template <class Iterator>
  struct iterator_traits {
    typedef typename Iterator::iterator_category iterator_category;
    typedef typename Iterator::value_type        value_type;
    typedef typename Iterator::difference_type   difference_type;
    typedef typename Iterator::pointer           pointer;
    typedef typename Iterator::reference         reference;
  };

Dla typów wskaźnikowych jest podana odpowiednia specjalizacja.

template <class T>
  struct iterator_traits<T*> {
    typedef random_access_iterator_tag iterator_category;
    typedef T                          value_type;
    typedef ptrdiff_t                  difference_type;
    typedef T*                         pointer;
    typedef T&                         reference;
  };

Widać więc, że każdy iterator nie będący wskaźnikiem musi mieć zdefiniowane odpowiednie typy stowarzyszone. Ułatwia to szablon klasy iterator, z którego można dziedziczyć:

namespace std {
template<class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator {
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
typedef Category iterator_category;
};

Na uwagę zasługuje typ iterator_category. Ten typ służy do automatycznego wyboru odpowiednich funkcji w oparciu o kategorię iteratora. Kategorie odpowiadają konceptom iteratorów i są reprezentowane przez puste klasy. W STL zdefiniowano pięć kategorii iteratorów:

namespace std {
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag {};
struct bidirectional_iterator_tag: public forward_iterator_tag {};
struct random_access_iterator_tag: public bidirectional_iterator_tag {};
}

Aby zilustrować zastosowanie typu iterator_category przedstawię implementację funkcji distance(), która oblicza odległość pomiędzy dwoma iteratorami. Potrzeba użycia iterator_category bierze się stąd, że dla iteratorów o dostępie swobodnym możemy policzyć ją bezpośrednio przez odejmowanie:

template <class _RandomAccessIterator>
inline 
typename iterator_traits<_RandomAccessIterator>::difference_type
__distance(_RandomAccessIterator __first, 
           _RandomAccessIterator __last,
             random_access_iterator_tag) {
return __last - __first; }

Dla reszty musimy po kolei zwiekszać jeden iterator aż osiągniemy drugi:

template <class _InputIterator>
inline 
typename iterator_traits<_InputIterator>::difference_type
__distance(_InputIterator __first, 
           _InputIterator __last, 
             input_iterator_tag)
{
  typename iterator_traits<_InputIterator>::difference_type __n = 0;
  while (__first != __last) {
    ++__first; ++__n;
  }
  return __n;
}

Do wyboru pomiędzy tymi dwoma implementacjami służy właśnie iterator_category:

template <class _InputIterator>
inline 
typename iterator_traits<_InputIterator>::difference_type
distance(_InputIterator __first, _InputIterator __last) {
typedef 
     typename 
     iterator_traits<_InputIterator>::iterator_category 
    _Category;
return __distance(__first, __last, _Category()); }

numeric_limits

Przykładem klasy cech zawartej w standardzie C++ jest szablon numeric_limits, który zastępuje używane w C makra, zdefiniowane w pliku limits.h. Szablon numeric_limits posiada specjalizację dla każdego typu podstawowego wbudowanego (zob. tab. 5.1]) i zawiera informację na temat różnych cech ich implementacji (zob. tab. 5.2]). Warto zwrócić uwagę na następującą konstrukcję: szablon numeric_limits definiuje stałą logiczną is_specialised. Domyślnie jest ona równa false. Każda specjalizacja szablonu ustawia ją na true. W ten sposób stała std::numeric_limits<T>::is_specialised mówi nam czy dany typ jest opisany przez numeric_limits czy nie.

namespace std {
template<class T> class numeric_limits;
enum float_round_style;
enum float_denorm_style;
template<> class numeric_limits<bool>;
template<> class numeric_limits<char>;
template<> class numeric_limits<signed char>;
template<> class numeric_limits<unsigned char>;
template<> class numeric_limits<wchar_t>;
template<> class numeric_limits<short>;
template<> class numeric_limits<int>;
template<> class numeric_limits<long>;
template<> class numeric_limits<unsigned short>;
template<> class numeric_limits<unsigned int>;
template<> class numeric_limits<unsigned long>;
template<> class numeric_limits<float>;
template<> class numeric_limits<double>;
template<> class numeric_limits<long double>;
}

Tablica 5.1. Specjalizacje szablonu numeric_limits

namespace std {
template<class T> class numeric_limits {
public:
static const bool is_specialized = false;
static T min() throw();
static T max() throw();
static const int digits = 0;
static const int digits10 = 0;
static const bool is_signed = false;
static const bool is_integer = false;
static const bool is_exact = false;
static const int radix = 0;
static T epsilon() throw();
static T round_error() throw();
static const int min_exponent = 0;
static const int min_exponent10 = 0;
static const int max_exponent = 0;
static const int max_exponent10 = 0;
static const bool has_infinity = false;
static const bool has_quiet_NaN = false;
static const bool has_signaling_NaN = false;
static const float_denorm_style has_denorm = denorm_absent;
static const bool has_denorm_loss = false;
static T infinity() throw();
static T quiet_NaN() throw();
static T signaling_NaN() throw();
static T denorm_min() throw();
static const bool is_iec559 = false;
static const bool is_bounded = false;
static const bool is_modulo = false;
static const bool traps = false;
static const bool tinyness_before = false;
static const float_round_style round_style = round_toward_zero;
};
}

Tablica 5.2. Szablon numeric_limits