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);
Явное инстанцирование используется
- Если инстанцирование шаблонов отнимает слишком много времени
- Если порядок компиляции должен быть абсолютно предсказуем
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;
}