Достаточно часто встречаются классы, объекты которых должны содержать элементы данных произвольного типа (в том смысле, что их тип определяется отдельно для каждого конкретного объекта). В качестве примера можно привести любую структуру данных (массив указателей, массив, список, дерево). Для этого в С++ предлагаются средства, позволяющие определить некоторое множество идентичных классов с параметризованным типом внутренних элементов. Они представляют собой особого вида заготовку класса, в которой в виде параметра задан тип (класс) входящих в него внутренних элементов данных. При создании конкретного объекта необходимо дополнительно указать и конкретный тип внутренних элементов в качестве фактического параметра. Создание объекта сопровождается созданием соответствующего конкретного класса для типа, заданного в виде параметра. Принятый в С++ способ определения множества классов с параметризованным внутренним типом данных (иначе, макроопределение) называется шаблоном (template).
Синтаксис шаблона рассмотрим на примере шаблона класса векторов, содержащих динамический массив указателей на переменные заданного типа.
// <class T> - параметр шаблона - класс "T", внутренний тип данных // vector - имя группы шаблонных классов template <class T> class vector { int tsize; // Общее количество элементов int csize; // Текущее количество элементов T **obj; // Массив указателей на парам. объекты типа "T" public: T *operator[](int); // оператор [int] возвращает указатель на // параметризованный объект класса "T" void insert(T*); // включение указателя на объект типа "T" int index(T*); };
Данный шаблон может использоваться для порождения объектов-векторов, каждый из которых хранит объекты определенного типа. Имя класса при этом составляется из имени шаблона "vector" и имени типа данных (класса), который подставляется вместо параметра "Т":
vector<int> a; vector<double> b; extern class time; vector<time> c;
Заметим, что транслятором при определении каждого вектора с новым типом объектов генерируется описание нового класса по заданному шаблону (естественно, неявно в процессе трансляции). Например, для типа int транслятор получит:
class vector<int> { int tsize; int csize; int **obj; public: int *operator[](int); void insert(int*); int index(int*); };
Далее следует очевидное утверждение, что функции- методы шаблона также должны быть параметризированы, то есть генерироваться для каждого нового типа данных. Действительно, это так: функции-методы шаблона классов в свою очередь также являются шаблонными функциями с тем же самым параметром. То же самое касается переопределяемых операторов:
// параметр шаблона - класс "T", внутренний тип данных // имя функции-элемента или оператора - параметризировано // template <class T> T* vector<T>::operator[](int n) { if (n >=tsize) return(NULL); return (obj[n]); } template <class T> int vector<T>::index(T *pobj) { int n; for (n=0; n<tsize; n++) if (pobj == obj[n]) return(n); return(-1); }
Заметим, что транслятором при определении каждого вектора с новым типом объектов генерируется набор методов- функций по заданным шаблонам (естественно, неявно в процессе трансляции). При этом сами шаблонные функции должны размещаться в том же заголовочном файле, где размещается определение шаблона самого класса. Для типа int сгенерированные транслятором функции-методы будут выглядеть так:
int* vector<int>::operator[](int n) { if (n >=tsize) return(NULL); return (obj[n]); } int vector<int>::index(int *pobj) { int n; for (n=0; n<tsize; n++) if (pobj == obj[n]) return(n); return(-1); }