1 шаблоны функций и классов

Понятие шаблона

Шаблоны (templates) - это механизм, позволяющий обобщать функции и классы для работы с различными типами данных.


Шаблоны обеспечивают непосредственную поддержку обобщенного программирования (т.е. c использованием типов в качестве параметров)


Шаблон зависит только от тех свойств параметра-типа, которые он явно использует


Существуют шаблоны функций и классов


Инстанцирование

Процесс порождения функции или класса из шаблона называется инстанцированием


Процесс генерации объявления класса по шаблону класса и аргументу шаблона


Версия шаблона для конкретного аргумента шаблона называется специализацией


Генерация версий шаблона - задача компилятора

1.1 Шаблоны функций

Для создания шаблона используется ключевое слово template. Также указывается пока неопределенный тип T.


Рассмотрим пример шаблонной функции swap для обмена значений двух переменных

template<class T> void swap(T& a, T&b)
{
   T  tmp = a;
   a = b;
   b = tmp;
}

Для получения функции проведем инстанцирование

void fun()
{
   int a=1, b=2;
   double c=1.1, d=2.2;
   swap<int>(a,b);      // все в порядке
   swap(c,d);           // тоже все хорошо
   swap(a,d);           // ошибка, разные типы a и d!
}

Еще один пример: функция сортировки

template< typename T >
void sort( T array[], int size );  // шаблон sort объявлен, но не определён
 
template< typename T >
void sort( T array[], int size )   // объявление и определение
{
  T t;
  for (int i = size - 1; i > 0; i--)
    for (int j = i; j > 0; j--)
      if (array[j] < array[j-1])  {
        t = array[j];
        array[j] = array[j-1];
        array[j-1] = t;
      }
}

Пример шаблона с целочисленным параметром

template< int BufferSize >         // целочисленный параметр
char* read()
{
  char *Buffer = new char[ BufferSize ];
  /* считывание данных */
  return Buffer;
}

Примеры использования шаблонов

int i[5] = { 5, 4, 3, 2, 1 };
sort< int >( i, 5 );
 
char c[] = "бвгда";
sort< char >( c, strlen( c ) );
 
sort< int >( c, 5 );    // ошибка: у sort< int > параметр int[] а не char[]
 
char *ReadString = read< 20 >;
delete [] ReadString;
ReadString = read< 30 >;

1.2 Шаблоны классов

Аналогично функциям можно создавать шаблоны классов. Рассмотрим пример стека:

template<class T> class Stack {
public:
  Stack(int size);
  ~Stack();
  Stack(const Stack&);
  void  push(T);
  T     pop();
  Stack& operator= (const Stack&);
private:
  int size;
  int top;
  T  *store;
};

Воспользоваться шаблоном класса можно так

void fun()
{
  Stack<char>   cStack;
  Stack<int>    iStack(100);
  Stack<double> dStack(50);
  cStack.push(‘a’);
  iStack.push(26);
  dStack.push(30.125);
  char c = cStack.pop();
  int  i = iStack.pop();
  double d = dStack.pop();
  iStack = dStack;  // а здесь ошибка!

Описание конструктора и деструктора шаблонного класса

// Конструктор
template<class T> Stack<T>::Stack(int _size) :
    size(_size), top(0)
{
    store = new T[size];
}
// Деструктор
template<class T> Stack<T>::~Stack()
{
    delete[] store;
}

Описание методов push и pop

// Помещение элемента в стек
template<class T> void Stack<T>::push(T value)
{
    if (top<size)
        store[top++] = value;
}
// Извлечение элемента из стека
template<class T> T Stack<T>::pop()
{
  return top ? store[--top] : 0;
}

Описание методов определения размера стека

// Максимальный размер стека
template<class T> 
inline int Stack<T>::get_max_size() const
{
    return size;
}

// Текущий размер
template<class T>
inline int Stack<T>::get_current_size() const
{
    return top;
}

Примеры инстанцирования

Рассмотрим примеры использования шаблона стека

int main()
{
  Stack<char>   cStack;
  Stack<int>    iStack(100);
  
  cStack.push(‘a’);
  iStack.push(26);

  cStack.pop();
  iStack.pop();

  int sz = iStack.get_max_size();
}

1.3 Параметры шаблонов

Параметры шаблона

У шаблонов могут быть параметры различных типов

template<class T, int size> 
class Buffer {
public:
  T* get(int i) {
  	return (i>=0 && i<size) ? &array[i] : 0; 
  }
private:
  T array[size];
};

Инстанцирование выполняется с указанием значения параметра

Buffer<char, 128> buf;

В шаблонах допускается использование различных видов параметров

template
    < class T1,                // параметр-тип
    typename T2,               // параметр-тип
    int I,                     // параметр обычного типа
    T1 DefaultValue,           // параметр обычного типа
    template< class > class T3,// параметр-шаблон
    class Character = char     // параметр по умолчанию
    >

Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:

  template< class Type, 
     template< class > class Container >
  class CrossReferences
  {
    Container< Type > mems;
    Container< Type* > refs;
    /* ... */
  };
 
  CrossReferences< Date, vector > cr1;
  CrossReferences< string, set > cr2;

Перегрузка шаблонов

template<class T> T sqrt(T);
template<class T> complex<T> sqrt(complex<T>);
double sqrt(double);

void fun(complex<double> z)
{
  sqrt(2);
  sqrt(2.0);
  sqrt(z);
}

Специализация шаблонов

template<class T> bool less(T a, T b)
{ 
   return a < b; 
}
template<> bool less<const char*>(const char* a,
                                  const char* b)
{ 
   return strcmp(a, b) < 0; 
}

template<> bool less(const char* a, const char* b)
{ 
   return strcmp(a, b) < 0; 
}
template<class T> void sort(vector<T>& v)
{
  if ( less(v[j+gap], v[j]) )  
     swap(v[j],v[j+gap])
  swap(v[j], v[j+gap]);
}

Явное инстанцирование

template class Stack<int>;
template void Stack<int>::push(int);
template void swap<String>(String, String);

Явное инстанцирование используется

  1. Если инстанцирование шаблонов отнимает слишком много времени
  2. Если порядок компиляции должен быть абсолютно предсказуем

2 Специализация шаблонов

Для чего нужна специализация шаблонов? Для того чтобы задать шаблон для отдельного значения параметра (типа или значения).


template<class T> bool less(T a, T b)
{ 
   return a < b; 
}
template<> bool less<const char*>(const char* a,
                                  const char* b)
{ 
   return strcmp(a, b) < 0; 
}

В приведённом примере создаётся шаблон функции сравнения двух элементов одного типа. Так можно сравнивать любые числа, но не строки.


Для строк создаётся специализация.


less(5,8);         // используется шаблон без специализации
less("abc","abd"); // используется шаблон со специализацией

2.1 Класс Bag

Шаблон класса Bag

В следующем примере приводится шаблон класса Bag, который является

динамическим контейнером элементов и его специализация,

позволяющая задавать элементы не по значению, а по указателю.


// partial_specialization_of_class_templates2.cpp
// compile with: /EHsc
using namespace std;

// Original template collection class.
template < class T > class Bag {
   T* elem;
   int size;
   int max_size;
public:
   Bag() : elem(0), size(0), max_size(1) {}

...
   void add(T t) {
      T* tmp;
      if (size + 1 >= max_size) {
         max_size *= 2;
         tmp = new T [max_size];
         for (int i = 0; i < size; i++)
            tmp[i] = elem[i];
         tmp[size++] = t;
         delete[] elem;
         elem = tmp;
      }
      else
         elem[size++] = t;
   }
...

...
   void print() {
      for (int i = 0; i < size; i++)
         cout << elem[i] << " ";
      cout << endl;
   }
};

template <class T> class Bag<T*> {
   T* elem;
   int size;
   int max_size;
public:
   Bag() : elem(0), size(0), max_size(1) {}
   void add(T* t) {
      T* tmp;
      if (t == NULL) {   // Check for NULL
         cout << "Null pointer!" << endl;
         return;
      }
...

...
      if (size + 1 >= max_size) {
         max_size *= 2;
         tmp = new T [max_size];
         for (int i = 0; i < size; i++)
            tmp[i] = elem[i];
         tmp[size++] = *t;  // Dereference
         delete[] elem;
         elem = tmp;
      }
      else
         elem[size++] = *t; // Dereference
   }
...

...
  void print() {
      for (int i = 0; i < size; i++)
         cout << elem[i] << " ";
      cout << endl;
   }
};

int main() {
   Bag<int> xi;
   Bag<char> xc;
   Bag<int*> xp; // Uses partial specialization for pointer types.

   xi.add(10);
   xi.add(9);
   xi.add(8);
   xi.print();

   xc.add('a');
   xc.add('b');
   xc.add('c');
   xc.print();
...

...
 int i = 3, j = 87, *p = new int[2];
   *p = 8;
   *(p + 1) = 100;
   xp.add(&i);
   xp.add(&j);
   xp.add(p);
   xp.add(p + 1);
   p = NULL;
   xp.add(p);
   xp.print();
}

2.2 Пример с наследованием

Мы можем использовать специализацию при наследовании.


Рассмотрим следующую ситуацию. Допустим мы хотим создать класс, услуги которого базируются на сходных по назначению, но отличных в их реализации классах BaseA и BaseB. Если оба базовых класса обладают схожим интерфейсом, то логично оформить наш класс в виде шаблона:

template  < typename  Base >
class  Derived  : public Base
{
};

если разработчику необходима информация о базовых классах объектов, основанных на шаблоне Derived. Как ее получить?

Следующий метод основан на введении вспомогательного шаблона класса (или структуры) BaseClassInstance, не содержащего ничего, кроме статической константы типа BaseClass, и специализированного для разных фактических типов базовых классов.


enum  BaseClass
{
     Unknown,
     BaseClassA,
     BaseClassB
};

template  < typename >
struct  BaseClassInstance
{
    static const BaseClass  value = Unknown;
};

template  <>
struct  BaseClassInstance<class BaseA>
{
    static const BaseClass  value = BaseClassA;
};

template  <>
struct  BaseClassInstance<class BaseB>
{
   static const BaseClass  value = BaseClassB;
};

Описываем шаблон класса-наследника, в который помещается метод GetBaseClass

template  < typename  Base >
class  Derived  : public Base
{
public:
	BaseClass GetBaseClass() const;
};
template  < typename  Base >
BaseClass Derived< Base >::GetBaseClass() const
{
    return BaseClassInstance< Base >::value;
}

Вот так можно воспользоваться созданными шаблонами:

class BaseA {};
class BaseB {};

int main()
{
	Derived<BaseA> da;
	Derived<BaseB> db;
	BaseClass b=db.GetBaseClass();
	return 0;
}