Przejdź do zawartości

Szablon (C++)

Z Wikipedii, wolnej encyklopedii

Szablon (ang. template) – element języka C++, umożliwiający programowanie uogólnione – tworzenie kodu niezależnego od typów, algorytmów oraz struktur danych.

Wyróżnia się:

Szablony w C++

[edytuj | edytuj kod]

Przykładowy szablon klasy w języku C++ wygląda następująco:

  template <class T> 
  class List
  {
      ...
  };

gdzie T jest parametrem szablonu, którego użyto w powyższej definicji zamiast konkretnego typu.

Powyższy wzorzec można wykorzystać następująco:

  List <int> lista1;
  List <std::string> lista2;
  List <MojaKlasa> lista_mojej_klasy;

Podczas kompilacji następuje tak zwana konkretyzacja szablonu (ang. template instantiation), podczas której kompilator na podstawie typów danych przekazanych wzorcowi generuje kod właściwy do obsługi danego typu. W przypadku powyżej zostaną wygenerowane 3 kopie kodu dla odpowiednich typów.

Jedną z najbardziej znanych i najbardziej udanych aplikacji programowania uogólnionego jest standardowa biblioteka szablonów (STL). Od 1998 roku stanowi ona fragment biblioteki standardowej języka C++ i zawiera definicje licznych algorytmów (np. sort) i pojemników (np. vector i list), które charakteryzują się wysoką sprawnością i łatwością użycia.

W języku C++ szablony stanowią też wygodny zamiennik dla makr preprocesora — nie tylko poprawiają czytelność kodu, ale także ułatwiają wyszukiwanie w nim błędów.

Zalety szablonów w C++

[edytuj | edytuj kod]

Wykorzystanie szablonów w programach pozwala skupić się bardziej na algorytmach niż na danych jakie są przetwarzane. Użycie szablonów pozwala zredukować ilość nadmiarowego kodu ponieważ jedną funkcjonalność można zaprogramować dla wielu typów danych. Następnie kompilator w konkretnym przypadku precyzuje typ i odpowiednio dostosowuje np. wywołania funkcji.

Krytyka szablonów w C++

[edytuj | edytuj kod]

Działanie szablonów w języku C++ przypomina bardzo zaawansowane makra preprocesora. Powoduje to, że kompilator ma zasadnicze trudności z wygenerowaniem prawidłowych, czytelnych komunikatów diagnostycznych w przypadku błędnego użycia poprawnego szablonu. Większość z nich dotyczy bowiem (poprawnego!) kodu bibliotecznego, co wprowadza programistę w konsternację. Jedyne użyteczne informacje diagnostyczne dotyczą miejsc konkretyzacji szablonu.

Powyższy problem spowodowany jest tym, że błędy z reguły dotyczą sposobu użycia szablonu, natomiast diagnostyka włączana jest dopiero po jego konkretyzacji, a więc zasadniczo w innym miejscu niż szablon ten jest faktycznie używany. Stąd wrażenie, że błędy znajdują się w bibliotekach, a nie w programie, który ich błędnie używa.

Pewną receptą na powyższy problem są tzw. koncepty. Obecnie są one popularnym rozszerzeniem wielu kompilatorów.

Metaprogramowanie w C++ przy użyciu szablonów

[edytuj | edytuj kod]

Jednym z prostszych przykładów metaprogramowania przy użyciu szablonów, jest szablon pozwalający na obliczenie wartości silni w czasie kompilacji.

  #include <iostream>
  
  template <int N>
  struct Silnia 
  {
      enum { wartosc = N * Silnia<N-1>::wartosc };
  };

  template <>             //specjalizacja kończy rekurencję
  struct Silnia<0> 
  {
      enum { wartosc = 1 };
  };
  
  int main() 
  {
      const int silnia4 = Silnia<4>::wartosc;     //jest to równoważne: const int silnia4 = 24;
      std::cout << silnia4;
      return 0;
  }

Pierwszy szablon Silnia parametryzowany jest typem int, wartość parametru przekazanego do szablonu jest następnie używana w definicji szablonu. Definicja ta zawiera rekurencyjne tworzenie instancji szablonu (Silnia<N-1>).

Drugi szablon, jest tak zwaną specjalizacją i jest użyty w celu zatrzymania rekurencyjnego wywoływania szablonu.

Przy kompilowaniu powyższego programu, w trakcie działania kompilatora tworzone są instancje szablonów od Silnia<0>, do Silnia<4>. Gdyby zadeklarować drugą zmienną typu const inicjalizowaną poprzez Silnia<3>::wartosc, kompilator nie będzie tworzył nowego szablonu, lecz skorzysta z szablonu stworzonego przy tworzeniu szablonu Silnia<4>. Należy pamiętać, że kompilator może zawierać ograniczenia na możliwą głębokość rekurencji.