Szablony

Szablony implementują koncepcję programowania generycznego (generic programming) - programowania używającego typów danych jako parametrów.

Szablony zapewniają mechanizm, za pomocą którego funkcje lub całe klasy mogą być implementowane dla ogólnych typów danych (nawet takich, które jeszcze nie istnieją).

Zastosowanie szablonów klas umożliwia parametryzację kontenerów lub algorytmów ze względu na typ ich elementów. W szczególnych przypadkach implementacja szablonów klas lub funkcji może być wyspecjalizowana dla pewnych typów. W przypadku użycia szablonów klas generowany jest kod tylko dla tych funkcji składowych, które rzeczywiście są wywoływane.

Parametry szablonów klas nie muszą być typami. Mogą być wartościami stałymi znanymi w chwili kompilacji posiadającymi wartości domyślne.

Szablony funkcji

Szablon funkcji - funkcja, której typy argumentów lub typ zwracanej wartości zostały sparametryzowane.

template<typename T>
const T& max(const T& a, const T& b)
{
  return a > b ? a : b;
}

int max1 = max(4, 9);
double x = 4.50;
double y = 3.14;
double max2 = max(x, y);

Definiowanie i wywoływanie szablonów funkcji

Tworzenie instancji szablonu - proces, w którym na podstawie szablonu generowany jest kod, który zostanie skompilowany.

Utworzenie instancji szablonu jest możliwe tylko wtedy, gdy dla typu podanego jako parametr szablonu zdefiniowane są wszystkie operacje używane przez szablon, np. operatory <, ==, !=, wywołania konkretnych metod, itp. Koncepcja szablonów wykracza poza zwykły model kompilacji (konsolidacji). Cały kod szablonu powinien być umieszczony w jednym pliku nagłówkowym. Dołączając następnie zawartość pliku nagłówkowego do kodu aplikacji umożliwiamy generację i kompilację kodu dla konkretnych typów.

Szablony funkcji a makrodefinicje C

Przykład makrodefinicji:

#define maximum(a, b) (((a) > (b)) ? (a) : (b))

Przykład szablonu:

template<typename T>
const T& max(const T& a, const T& b)
{
  return a > b ? a : b;
}

int a = 5;
int b = 10;
max(a++, --b);

Specjalizacja funkcji szablonowych

W specjalnych przypadkach istnieje możliwość zdefiniowania funkcji specjalizowanej.

// szablon
template<typename T>
const T& max(const T& a, const T& b);

// funkcja specjalizowana
char* max(char* a, char* b);

max(4, 5); // wywołanie funkcji szablonowej
//...
max("a", "g"); // wywołanie funkcji specjalizowanej

Kolejność:

  • Definicja szablonu

  • Deklaracja (definicja) funkcji specjalizowanej

  • Wywołania

Inny przykład specjalizacji:

template<class T> T sqrt(T);
template<class T> complex<T> sqrt(complex<T>);
double sqrt(double);
//...
void f(complex<double> z)
{
   sqrt(2);   // sqrt<int>(int)
   sqrt(2.0);         // sqrt(double)
   sqrt(z);   // sqrt<double>(complex<double>)
}

Przeciążanie szablonów

W programie może obok siebie istnieć mając tę samą nazwę:

  • kilka szablonów funkcji – byle produkowały funkcje o odmiennych argumentach,

  • funkcje, o argumentach takich, że mogłyby zostać wyprodukowane przez któryś z szablonów (funkcje specjalizowane),

  • funkcje o argumentach takich, że nie mógłby ich wyprodukować żaden z istniejących szablonów (zwykłe przeładowanie).

Adres wygenerowanej funkcji

Możliwe jest pobranie adresu funkcji wygenerowanej na podstawie szablonu.

template <typename T> void f(T* ptr)
{
   cout << "funkcja szablonowa f(T*)" << endl;
}

void h(void (*pf)(int*))
{
   cout << "h( void (*pf)(int*)" << endl;
}

int main()
{
   h(&f<int>);    // przekazanie adresu funkcji wygenerowanej
                  // na podstawie szablonu
}

Podsumowanie

  • Szablony są schematami kodu kompilowanego po wybraniu określonego typu danych

  • Tworzenie kodu w języku C++ na podstawie szablonu nazywa się tworzeniem instancji szablonu

  • Szablony mogą posiadać wiele parametrów

  • Szablony funkcji mogą być przeciążane

Szablony klas

W taki sam sposób jak były parametryzowane typy funkcji, mogą być również parametryzowane typy użytkownika – klasy.

W terminologii obiektowej szablony klas nazywane są klasami parametryzowanymi.

Szablony klas mogą być wykorzystane do implementacji kontenerów, dla których typ nie jest jeszcze znany.

template <typename T>
class Pair
{
    T values_[2];

public:
    Pair (T first, T second);
    {
        values_[0]=first;
        values_[1]=second;
    }

    const T& get_max() const;
};
template <class T>
const T& Pair<T>::get_max() const
{
    T retval = a > b ? a : b;
    return retval;
}
Pair<int> myInts(3, 5);
Pair<float> myFloats(2.11, 3.14);

Implementacja funkcji składowych

Definiując funkcję składową szablonu klasy należy określić jej przynależność do szablonu.

template <typename T>
class Pair
{
private:
  T values_[2];
public:
  Pair(T first, T second);
  const T& get_max() const;
  void swap();
  //...
};

template <typename T>
Pair<T>::Pair<T>(T first, T second)
{
   //...
}

template <typename T>
const T& Pair<T>::get_max() const
{
   //...
}

template <typename T>
void Pair<T>::swap()
{
   //...
}

Parametry szablonów

Każdy parametr szablonu może być:

  1. Typem (wbudowanym lub zdefiniowanym przez użytkownika).

  2. Stałą w chwili kompilacji (liczby całkowite, wskaźniki i referencje danych statycznych).

  3. Innym szablonem.

Parametry szablonów niebędące typami

Można używać parametrów nie będących typami, o ile są to stałe liczby całkowite znane na etapie kompilacji.

template <class T, size_t N>
class Stack
{
public:
    Stack();
    //...
private:
    size_t size_;
    T elems_[N];
};

Domyślne parametry szablonu

Parametrom szablonu klasy można przypisać argumenty domyślne (nie jest to możliwe dla szablonów funkcji).

template <class T, size_t N = 100>
class Stack
{
public:
    Stack();
    //...
private:
    size_t size_;
    T elems_[N];
};

template <typename T, typename CONT = vector<T> >
class Stack;

Szablony jako parametry szablonów

Jeżeli w kodzie jako parametr ma być użyty inny szablon, to kompilator powinien zostać o tym poinformowany.

template <typename T, template<typename> class CONT>
class Stack
{
private:
    CONT<T> elems_;
public:
    Stack();
    void push(const T& elem);
    void pop();
    T top() const;
};

template <typename T, template<typename> class CONT>
Stack<T, CONT>::Stack()
{
}

Specjalizacja szablonów klas

Specjalizacja szablonów klas – polega na osobnej implementacji szablonów dla różnych typów. Specjalizacja umożliwia optymalizację implementacji dla wybranych typów lub uniknięcie niepożądanego zachowania na skutek utworzenia instancji szablonu dla wybranego typu.

template <typename T> class Pair<T> { /* //... */ };  // szablon ogólny
template<typename T> class Pair<T*> { /* //... */ };  // częściowa specjalizacja
template<> class Pair<const char*>;     // specjalizacja szczegółowa

Możliwe jest tworzenie częściowych specjalizacji szablonów.

Dla szablonu klasy…

template <class T1, class T2>
class MyClass {
  //...
};

możemy utworzyć następujące specjalizacje częściowe:

template <class T>
class MyClass<T, T> {
  //...
  // specjalizacja częściowa: drugim typem jest T
};
template <class T>
class MyClass<T, int> {
    //...
    // specjalizacja częściowa: drugim typem jest int
};
template <class T1, class T2>
class MyClass<T1*, T2*> {
    //... // oba parametry są wskaźnikami
};

Słowo kluczowe typename

Słowo kluczowe typename umożliwia określenie , że dany symbol (identyfikator) występujący w kodzie szablonu jest typem, np. typem zagnieżdżonym – zdefiniowanym wewnątrz klasy.

template <class T>
class X
{
    typename T::id i;
public:
    void f() { i.g(); }
};

class Y
{
public:
    class id {
    public:
        void g() {}
    };
};

int main()
{
    X<Y> xy;
    xy.f();
}

Składowe jako szablony

Składowe klas mogą być szablonami. Dotyczy to:

  • wewnętrznych klas pomocniczych,

  • funkcji składowych.

template <typename T>
class Stack {
    std::deque<T> elems_;
public:
    void push(const T&);
    void pop();
    //...
    // przypisanie stosu o elementach typu T2
    template <typename T2>
    Stack<T>& operator=(const Stack<T2>&);
};

template <typename T>
    template <typename T2>
Stack<T>& Stack<T>::operator=(const Stack<T2>& source)
{
    //...
}