Пример. Представим, что нам необходимы стек целых чисел и стек контроллеров, управляющих температурой. Мы могли бы описать два стека:
class IntStack {
int stack[100];
...
};
class ControllerStack {
Controller* stack[100];
...
};
Другой, более разумный, подход – создать универсальный стек, который мог бы хранить элементы любого нужного нам типа. Для этого мы можем описать стек, содержащий указатели на нетипизированные элементы:
class Stack {
void* stack[100];
...
};
Однако это не безопасно с точки зрения типов. Никто не гарантирует нам, что пользователь не поместит в стек элемент одного типа, а взять захочет элемент другого типа.
Для реализации нашей идеи необходимо воспользоваться шаблоном или параметризованным классом. Шаблон служит для построения других классов и может быть параметризован другими классами, объектами или операциями.Использование шаблонов реализует в языке С++ особый тип полиморфизма – параметрический полиморфизм.
template <class Type> class Stack {
Type stack[100];
...
public:
void push(Type);
Type pop();
...
};
...
|
|
template <class Type> void Stack <Type>:: push(Type el) {...}
template <class Type> Type Stack <Type>:: pop() {...}
Префикс template <class Type> делает Type параметром объявления, которому этот префикс предшествует.
Инстанцирование – подстановка фактических параметров шаблона вместо формальных. В результате создается конкретный класс, который может иметь экземпляры.
Объявим нужные нам стеки:
typedef Stack < int > IntStack // синоним класса стеков целых чисел
typedef Stack < Controller* > ControllerStack // синоним класса стеков
// контроллеров
IntStack IS; // стек для целых чисел
ControllerStack CS; // стек для контроллеров
Объекты IS и CS – это экземпляры совершенно различных классов, которые даже не имеют общего суперкласса. Тем не менее они получены из одного параметризованного класса Stack.
Инстанцирование безопасно с точки зрения типов. По правилам C++ будет отвергнута любая попытка поместить в стек или извлечь из него что-либо, кроме целых чисел или указателей на экземпляры класса Controller соответственно.
В языке С++ можно определять шаблоны не только классов, но и функций. В качестве примера рассмотрим определение шаблона функции, служащей для определения максимального из двух элементов.
template <class Type > Type max(Type x, Type y){
return (x > y)? x: y;
};
Теперь мы можем использовать один и тот же шаблон для целых и вещественных чисел.
int i = 1, j = 2, k;
double a = 1.5, b = 1.2, c, d;
k = max(i, j); c = max(a, b); d = max <double> (i, a);
Кроме того, можно использовать этот шаблон и для объектов некоторого класса, если в нем переопределен оператор «>».
Каждая версия класса или функции, создаваемая по шаблону, содержит одинаковый базовый код; изменяется только то, что связано с параметрами шаблона. При этом эффективность работы версий, создаваемых для различных типов данных, может сильно различаться. Если для какого-либо типа данных существует более эффективный код, можно либо предусмотреть для этого типа специальную реализацию отдельных операций, либо полностью переопределить (специализировать) шаблон класса.
|
|
Так, для специализации операции требуется определить вариант ее кода, указав в заголовке конкретный тип данных.
void Stack < Circle*>:: push(Circle* el) {...}
При специализации целого класса после описания обобщенного варианта класса помещается полное описание специализированного класса, при этом требуется заново определить все его методы:
class Stack < Circle*> {...}