Шаблоны

advertisement
Шаблоны

Шаблоны позволяют давать обобщенные определения классов и
функций. Эти определения затем могут служить компилятору
основой для классов или функций, создаваемых для конкретного типа
данных

Шаблоны являются эффективным способом реализации процедур,
которые раньше обычно много раз переписывались для данных
различного типа

Шаблоны С++ часто называют параметризованными типами. Они
позволяют вам компилировать новые классы или функции, задавая
типы в качестве параметров
Шаблоны функций

Шаблон функции представляет собой обобщенной определение, из
которого компилятор может автоматически генерировать код
(создать представитель функции)

Шаблон функции является определением шаблона, из которого
компилятор может создавать функции

Функции, созданные по шаблону, называют иногда функциями
шаблона (шаблонными функциями)
Шаблоны функций
Синтаксис следующий:
template <список_аргументов_шаблона>
возвр_тип имя_функции(параметры)
{
//Тело функции
}
// Шаблон функции func воспринимает один параметр произвольного типа
template <class T>
void func(T t)
{
// Тело функции
}
// Шаблон функции func_1 воспринимает два параметра одного типа
template <class T>
void func_1(T t1, T t2)
{
// Тело функции
}
// Шаблон функции воспринимает два параметра различного типа
template <class T1, class T2>
void func_2(T1 t1, T2 t2)
{
// Тело функции
}

За ключевым словом template следуют один или несколько
аргументов, заключенных в угловые скобки и отделенные друг от
друга запятыми

Каждый аргумент состоит из ключевого
идентификатора, обозначающего тип

Определение функции похоже на обычное определение функции за
исключением того, что один или несколько параметров используют
типы, специфицированные в списке аргументов шаблона
слова
class
и
//
// Шаблон функции swap воспринимает массив
// произвольного типа и два целых числа
//
template <class T>
void swap(T t[ ], int indx1, int indx2 )
{ T tmp = t[indx1];
t[indx1]=t[indx2];
t[indx2]=tmp;
}

Часто приходится сортировать элементы массива. Можно
использовать шаблон функции и написать обобщенное определение
для сортировки массивов любых типов
// Для сортировки массива определенного пользователем
//типа должна быть перегружена операция '>'
// file - sortmpl.h
//
template <class T>
void sort(T array[], size_t size)
{ for (int i=0; i<size-i; i++)
for (int j=size-1; i>j; j--)
if ( array[j-1] > array[j] )
{ T tmp = array[j];
array[j]=array[j-1];
array[j-1]=tmp;
}
}

Можно создать прототип шаблона функции в виде его
предварительного объявления. Такое объявление информирует
компилятор о наличии шаблона и об ожидаемых параметрах
Например:
template <class T>
void sort((T array[], size_t size);

После определения шаблона функции можно сразу ее вызвать.
Компилятор автоматически создаст представитель тела функции для
указанных в вызове типов
#include <iostream.h>
#include "sorttmpl.h"
int main()
{ int iarray[]={ 30, 27, 45, 10, 23, 7, 4, 89 };
sort( iarray, sizeof(iarray)/sizeof(iarray[0]) );
for (int i=0; i< sizeof(iarray)/sizeof(iarray[0]); i++)
cout << "iarray[" << i << "] = << iarray[i] << endl;
return 0;
}

Шаблон sort можно применять и для классов, определенных
пользователем, при условии, что класс перегружает операцию '>',
используемую в шаблоне для сравнения элементов
#include <iostream.h>
#include "sorttmpl.h"
class Amount
{ long dollars;
int cents;
public:
Amount(long _d, int _c) { dollars = _d; cents - _c; }
int operator > (const Amount&);
friend ostream& operator << (ostream&, Amount&);
};
int Amount::operator > (const Amount& _amt )
{
return (dollars > _amt.dollars) ||
(dollars == _amt.dollars && cent > _amt.cents );
}
ostream operator << ( ostream& os, Amount &_amt)
{
os << "$" << _amt.dollars << '.' << _amt.cents;
return os;
}
int main(void)
{
Amount amtArray = { Amount(19,10);
Amount(19,10);
Amount(19,10);
Amount(19,10);
};
sort(amtArray, sizeof(amtArray)/aiseof(amtArray[0]) );
for (int i=0; i< sizeof(amtArray)/sizeof(amtAarray[0]); i++)
cout << "amtAarray[" << i << "] = << amtArray[i] << endl;
return 0;
}

Файл stdlib.h, входящий в состав BORLAND C++, содержит
определения шаблонов функций min и max, которые используются в
режиме компиляции С++

Эти шаблоны позволяют компилятору автоматически создавать
варианты-представители функций min и max всякий раз, когда они
вызываются с двумя параметрами одного типа
template <class T>
inline const T& min(const T& t1, const T& t2) { return t1>t2?t2:t1; }
inline const T& max(const T& t1, const T& t2) { return t1>t2?t1:t2;}

Компилятор создает и вызывает шаблонную функцию.

Тип параметров может быть встроенным или определенным
пользователем с перегруженной функцией >( )
#include <stdlib.h>
#include <iostream.h>
#include <classlib\date.h>
int main()
{ int i1=10, i2=20;
cout << "Max= " << max(i1,i2) << endl;
float f1=3.456, f2=3.789;
cout << "Max= " << max(f1,f2) << endl;
Tdate d1(8, 8, 65), d2(10, 3, 69);
Tdate d3=max(d1,d2);
cout << "Max дата равна " << d3 << endl;
return 0;
}

Точно так же как и обычные функции, функциональные шаблоны
могут быть перегружены. Т.е. можно предусмотреть несколько
шаблонов функций с одним и тем же именем, если только они имеют
различные заголовки (различное число или различные типы
параметров)
#include <iostream.h>
template <class T>
T getMax ( T t1, T t2) { return t1>t2?t1:t2; }
template <class T>
T getMax ( T t[ ], size_t size)
{ T retVal = t[0];
for (int i=0; i<size; i++) if (t[i] > retVal) retVal=t[i];
return retVal;
}
int main()
{ int i1=3, i2=5; int iarray[ ]={3, 9, 5, 8 };
cout << "max int =" << getMax(i1,i2) << endl;
cout << "max int =" << getMax(iarray, sizeof(iarray)/sizeof(iarray[0])) << endl;
return 0;
}

Специализированная функция шаблона - это обычная функция,
имя которой совпадает с именем функции в шаблоне, но определена
для других типов. Например getMax не может применяться для строк
#include <iostream.h>
#include <string.h>
template <class T>
T getMax ( T t1, T t2) { return t1>t2?t1:t2; }
char* getMax(char *s1, char *s2) // Специализированная функция
{ return strcmp(s1,s2) > 0 ? s1 : s2;}
int main()
{ int i1=3, i2=5;
cout << "max int =" << getMax(i1,i2) << endl;
char *s1 = "Golden Eagle";
char *s2 = "perigrine Falcon";
cout << "max str =" << getMax(s1,s2) << endl;
return 0;
}

Когда компилятор встречает обращение к функции, для разрешения
ссылки используется следующий алгоритм:
 Найти функцию (не шаблонную), параметры которой
соответствуют указанным в вызове
 Если функция не найдена, найти шаблон, из которого можно
генерировать функцию с точным соответствиям параметров
 Если никакой шаблон не обеспечивает точного соответствия,
снова рассмотреть обычные функции на предмет возможного
преобразования типов

Преобразование типа параметров не производится при
рассмотрении шаблонов.
Шаблоны классов

Шаблон
класса
дает
обобщенное
определение
использующих произвольные типы или константы
классов,

Шаблон определяет элементы данных и элементы-функции класса

После определения шаблона класса вы можете предписать
компилятору генерировать на его основе новый класс для
конкретного типа или константы.

Синтаксис имеет следующий вид:
template <список_аргументов_шаблона>
class имя_класса
{
// Тело класса
};

За ключевым словом template следуют один или несколько
аргументов, заключенных в угловые скобки и отделяемые друг от
друга запятыми

Каждый аргумент является:



либо именем типа, за которым следует идентификатор
либо ключевым словом class, за которым следует
идентификатор, обозначающий параметризованный тип
Затем следует определение класса. Оно аналогично определению
обычного класса, за исключением того, что используется список
аргументов шаблона
#include <stdlib.h>
const size_t defStackSize = 10;
//
// Шаблон класса для простых стековых операций
//
template <class T>
class Tstack
{ public:
Tstack(size_t size=defStackSize )
{ numItems = 0; items = new T[size]; }
~Tstack() { delete [ ] items; }
void push(T t);
T pop();
protected:
int numItems;
T *items;
};
template <class T>
void Tstack::push(T t) { items[numItems++] = t; }
template <class T>
T Tstack <T>::pop() { return items[--numItems]; }

Как видно из примера, функции-элементы шаблона класса могут
определяться в теле класса (то есть как встроенные)

Однако, если функция определяется как не-встроенная, ее заголовок
имеет следующий формат:
template <список_аргументов_шаблона>
возвр_тип имя_класса<арг_шаблона>::имя_функции(параметры...)
{
// Тело функции
}

Можно применить ключевое слово
встроенного метода вне тела шаблона:
inline
template <class Type>
inline void className<Type>::methodName(int i)
{
// Тело функции
}
для
определения

Параметры шаблона класса могут быть либо типами, либо
константами

Нельзя объявить внутренний шаблон в теле другого шаблона класса.
Однако можно объявить внутренний класс, который использует один
или несколько параметров шаблона

Чтобы создать представитель шаблонного класса, можно просто
указать имя шаблона со списком аргументов, заключенным в угловые
скобки, в качестве спецификатора типа.

Список аргументов шаблона модифицируется следующим образом:



каждый аргумент, имеющий вид 'class идентификатор' (параметр типа),
заменяется именем действительного типа
каждый аргумент, имеющий вид 'имя_типа_идентификатор' (нетиповой
параметр), заменяется константным выражением
Пример показывает порождение различных представителей класса:
#include <cstring.h>
#include "clstmpl1.h"
Tstack<int> stckInt1;
Tstack<int> stckInt2(40);
Tstack<long> *pstckLng;
Tstack<double> dblStk[10];
Tstack<string> strStk;

// Определение TStack
// Стек int значений (размер по умолчанию)
// вмещающий до 40 элементов
// Указатель на стек для long
// Массив стеков для double
// Стек для string, определенный
// пользователем(см. String)
Создав представитель шаблонного класса, можно обращаться с ним
точно так же, как с принадлежащими к обычному классу
#include <cstring.h>
#include "clstmpl1.h" // Определение TStack
int main(void)
{ TStack<int> Stack0fInf(10);
Stack0fInf.push(33);
Stack0fInf.push(44);
Stack0fInf.push(55);
cout << "Помещенные значения равны: " << Stack0fInf.pop()
<<',' << Stack0fInf.pop() <<',' << Stack0fInf.pop() << endl;
Tstack<double> Stack0fDouble(10);
Stack0fDouble.push(2.3456);
Stack0fDouble.push(4.8967);
Stack0fDouble.push(-1.345);
cout << "Помещенные значения равны: «
<< Stack0fDouble.pop() <<',' << Stack0fDouble.pop() <<','
<< Stack0fDouble.pop() << endl;
return 0;
}

Можно применить к шаблонному классу ключевое typedef, чтобы
упростить его использование. Например:
#include "clstmpl1.h"
TStack<long> lstck;
Tstack<long> *plstck;
Tstack<long> alstck[10];

Упрощается до
#include "clstmpl1.h"
typedef TStack<long> LStak;
LStak lstck;
LStak *plstck;
LStak alstck[10];
Недостатки шаблонов

Шаблоны С++ приносят программисту определенные выгоды, такие
как унификация и малые затраты на сопровождение кода, и в общем
весьма эффективна по сравнению с другими методами (такими как
полиморфизм), применяемыми в обращении с различными типами
данных

Шаблоны, кроме того обеспечивают безопасное использование
типов, в отличие от макросов препроцессора

Однако существуют и отрицательные стороны


Программа будет содержать полный код представителей
шаблона для каждого из порождаемых типов. Это может сильно
увеличить размер исполняемого модуля
Часто реализация шаблона хорошо работает с некоторыми
типами, но далека от оптимальной в других случаях

Например:
template <class T>
inline const T& min(const T& t1, const T& t2) { return t1>t2?t2:t1; }
inline const T& max(const T& t1, const T& t2) { return t1>t2?t1:t2; }

Использование ссылок заставляет компилятор генерировать
дополнительный код косвенных обращений. Однако такой шаблон
хорошо подходит для больших объектов, копирование которых было
бы дорогостоящим.
Download