4 Параметрический полиморфизм и шаблоны

4.1 Параметрический полиморфизм

С++ использует ключевое слово template для того, чтобы обеспечить параметрический полиморфизм. Параметрический полиморфизм позволяет одному и тому же коду использоваться относительно различных типов, где тип - параметр тела кода. Параметрический полиморфизм особенно полезен при определении контейнерных классов. Обработка данных в контейнерном классе имеет одну и ту же форму, независимо от типа. Шаблоны определения класса и шаблоны определения функции дают возможность многократно использовать код простым способом, безопасным по отношению к типу, который позволяет компилятору автоматизировать процесс реализации типа. Полиморфизм обеспечивает многократное использование кода.

4.2 Шаблоны

Довольно часто классы создаются для объединения множества элементов данных, которые внутри объекта могут быть связаны массивом ссылок, списком, деревом и т.д.. При этом объект класса содержит ссылки.

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

Принятый в Си++ способ определения множества классов с параметризованным внутренним типом данных (иначе, макроопределение) называется шаблоном (template). Синтаксис шаблона рассмотрим на примере шаблона класса векторов, содержащих динамический массив ссылок на переменные заданного типа.

--- параметр шаблона - класс "T", внутренний

¦ тип данных

¦ --- имя группы шаблонных классов

template <class T> class vector

{

int tsize; // Общее количество элеметов

int csize; // Текущее количество элементов

T **obj; // Массив ссылок на параметризованные объекты типа "T"

public:

T *operator[](int); // оператор [int] возвращает ссылку

// на параметризованный объект класса "T"

void insert(T*); // функция включения объекта типа "T"

int extract(T*); //

};

Данный шаблон может использоваться для порождения объектов-векторов, каждый из которых хранит объекты определенного типа. Имя класса при этом составляется из имени шаблона "vector" и имени типа данных (класса), который подставляется вместо параметра "Т":

vector<int> a;

vector<double> b;

extern class time;

vector<time> c;

Заметим, что транслятором при определении каждого вектора с новым типом объектов генерируется описание нового класса по заданному шаблону (естественно, неявно в процессе трансляции):

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* 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);

}

Шаблоны могут иметь также и параметры-константы, которые используются для статического определения размерностей внутренних структур данных. Кроме того, шаблон может использоваться для размещения не только ссылок на параметризованные объекты, но и сами объекты. В качестве примера рассмотрим шаблон для построения циклической очереди ограниченного размера для параметризованных объектов.

template <class T><int size> class FIFO {

int fst,lst; // Указатели на начало-конец очереди

T queue[size]; // Массив объектов класса "T" размерности "size"

public:

T from(); // Функции включения-исключения

void into(T);

FIFO(); // Конструктор

};

template <class T><int size> FIFO<T><int>::FIFO() { fst = lst = 0; }

template <class T><int size> T FIFO<T><int>::from() {

T work;

if (fst !=lst) {

work = area[lst++];

lst = lst % size;

}

return(work);

}

template <class T><int size> void FIFO<T><int>::into(T obj) {

area[fst++] = obj;

fst = fst % size;

}

Пример использования:

FIFO<double><100> a;

FIFO<int><20> b;

struct x {};

FIFO<x><50> c;

Пример сгенерированного компилятором класса для объекта "a".

class FIFO<double><100> {

int fst,lst;

double queue[100];

public:

double from();

void into(double);

FIFO();

};

FIFO<double><100>::FIFO() {

fst = lst = 0;

}

double FIFO<double><100>::from() {

double work;

if (fst !=lst) {

work = area[lst++];

lst = lst % 100;

}

return(work);

}

void FIFO<double><100>::into(double obj) {

area[fst++] = obj;

fst = fst % 100;

}

В языке С++ различают шаблоны функций и шаблоны классов.

Иногда возникает ситуация, когда полезна уже существующая функция, но с другими типами параметров. В языке С такая проблема может быть решена с помощью макросов, но это не идеальное решение. Дело в том, что при использовании макросов не выполняется проверка типов данных. Например, рассмотрим макрос, работающий со всеми типами данных:

#define max(a,b) ((a > b)? a: b)

Макрос может быть вызван с разными типами параметров, например, целым и указателем на char, но сообщения об ошибке не последует.

В С++ используются шаблонные функции. Универсальная для всех типов параметров функция определения максимального значения может быть записана с помощью шаблона следующим образом:

template < class T > T max ( T a, T b) { return a > b ? a : b; }

class T называется параметром шаблона. При использовании функции max с конкретными типами данных создается компилятором шаблонная функция. Именно эта сгенерированная компилятором функция имеет код, и именно она вызывается.

void main() {
// будут сгенерированы две разные функции,
// одна - для обработки целых,
// другая - для обработки символов
int k = max ( 33,55 );
сhar ch = max ( '3','5');
// а здесь будет сгенерировано сообщение об ошибке,
// так как типы параметров различны
int i = max (33,'3');
}

Шаблонные функции можно перегружать. Например, если определить функцию max для обработки указателей на char, то при вызове функции будет использоваться явно указанная функция.

char max(char * pa, char * pb) { return (*pa > * pb) ? *pa : * pb; }
void main() {
char * pc1 = "q";
char * pc2 = "z"; // будет вызвана функция, определенная выше
char ch = max ( pc1,pc2); // будет сгенерирована функция из шаблона
double f = max ( 1.5,2.0);
}

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

template < class T >
class Vector {
T * data;
int size;
public:
Vector(int);

~Vector() { delete data; }
T & operator[] ( int i ) { return data[i]; }
};
// определение конструктора вне описания шаблона класса
template < class T >
Vector < T > :: Vector ( int s )
{ data = new T [s];
size = s;
}

После определения шаблона программист может определить конкретные объекты классов, которые компилятор будет порождать из шаблона. Например,

void main() {
// создаются два класса для разных типов данных
// создаются объекты этих классов
Vector <int> xint(15);
Vector <cha> xchar(128);
for ( int i = 0; i < 15; i++ )
xint[i] = i;
for ( i = 0; i < 128; i++ )
xchar[i] = i+40;
}

В списке параметров шаблона могут присутствовать формальные параметры с фиксированными типами:

template < class T, int size = 80 >

class Str {
T * data;
int len;
public:
Str(){
len = size;
data = new T [ len];
}
~Str() { delete data;}
T & operator [] ( int i ) { return data[i]; }
};
void main() {

Str < double, 15 > xd;

Str < char > xch;
for ( int i = 0; i < 15; i++ )
xd[i] = i * 0.5;
for ( int i = 0; i < 80; i++ )
xch[i] = 'a'+ i;
}

Вопросы для самопроверки.

1. Может ли список параметров шаблона включать несколько параметров? Может ли список параметров шаблона быть пустым?

2. Можно ли в списке параметров шаблона функции использовать встроенные типы, например, int, double и т.д.

3. Найдите ошибку в шаблоне функции:
template < class A, class B, class C >
B func ( A n, C m ) {
B value;
// ...
}

4. Может ли определение шаблона функции быть локальным в каком-либо блоке? Подтвердите ваш ответ примером.

5. Верна ли запись вида:

template < class T >
class A {
friend class B<A>;
public:
A();
//
};
template < class T >
class B {
public:
B();
//
};

6. Может ли шаблонный класс использоваться в другом шаблонном классе? Приведите пример.

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

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

Часто встречаются функции, реализующие одни и те же действия для аргументов различных типов. Например, сортировка массива по возрастанию его элементов может выполняться одним и тем же методом и для данных типа int и для данных типа double. Различие состоит только в типах параметров и некоторых внутренних переменных.

В более поздние версии С++ включено специальное средство, позволяющее параметризовать определение функции, чтобы компилятор мог построить конкретную реализацию функции для указанного типа параметров функции. Параметризованное определение функции строится по схеме:

template < class имя_класса >

Заголовок функции

{ /* Тело функции */ }

Имя класса является параметром и задается идентификатором, локализованным в пределах определения функции. Хотя бы один из параметров функции должен иметь тип, соответствующий этому идентификатору.

Параметризованное определение функции сортировки массива методом перестановок может быть построено следующим образом:

template <class T >

void sort ( T a[ ], int n ){

T temp;

int sign;

for ( int k = 0; k > n; k++)

{ sign = 0;

for ( i = 0; i <n - k; i++)

if ( a [ i ] > a [ i + 1])

{ temp = a [ i ];

a[ i ] = a[ i + 1 ];

a[ i + 1 ] = temp; sign++;

}

if ( sign == 0 ) break;

}

return;

}

Если в программе будут объявлены массивы

int aint [10];

double afl [20];

и установлены значения элементов этих массивов, то вызов функции sort ( aint, 10 ); обеспечит вызов sort для упорядочения массива целых, а вызов функции sort ( afl , 20 ) обеспечит вызов sort для упорядочения массива с элементами типа double.

Если элементами массива являются объекты какого-либо определенного программистом класса, для которого определена операция отношения >, то функция sort может быть вызвана и для такого массива. Разумеется, в объектном коде программы будут присутствовать все варианты реально вызывамой функции sort. Параметризация функции сокращает объем исходного текста программы и повышает его надежность.

В описателе template можно указывать несколько параметров вида class имя_типа, а также параметры базовых типов. Например, функция

template < class T1, class T2 >

void copy ( T1 a[ ], T2 b[ ], int n)

{ for ( int i = 0; i <n; i++)

a[ i ] = b [ i ] ;

}

копирует первые n элементов массива b типа T2 в первые n элементов массива a типа T1. Разумеется, программист несет ответственность за то, чтобы такое копирование было возможным.

Большинство функций имеют одно и то же тело кода, независимо от типа. Например, инициализация содержимого одного массива от другого того же самого типа. Обычно это код выглядит так:

for(i=0; i<n; i++) a[i]=b[i];

Данный код можно на С автоматизировать простой макрокомандой.

#define COPY(A,B,N) { int i; for(i=0; i<N; i++) A[i]=B[i]; }

Она работает, но не безопасно по отношению к типу. Пользователь легко смешивает типы при несоответствующих преобразованиях. Для достижения подобных эффектов на С++ можно использовать различные формы преобразования и перегрузки. Однако, при отсутствии соответствующих преобразований и сигнатур, не будет предприниматься никаких действий. Шаблоны обеспечивают для этого следующий полиморфный языковый механизм:

template <class TYPE>

void copy(TYPE a[],TYPE b[],int n) {

for(int i=0; i<n; i++) a[i]=b[i];

}

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

double f1[50], f2[50];

copy(f1,f2,50);

char c1[25],c2[50];

copy(c1,c2,10);

int i1[75],i2[75];

copy(i1,i2,40);

char *ptr1, *ptr2;

copy(ptr1,ptr2,100);

copy(i1,f2,50); // error

copy(ptr1,f2,50); // error

Последние два вызова приведут к ошибке компиляции. Типы фактических параметров не соответствую шаблону. Запись следующего вида не вызовет ошибку

copy(i1,(int *)f2,50);

Однако при этом будет получена несоответствующая форма копирования. Дело в том, что родовая процедура копирования должна получать в виде параметров два класса отличающегося типа.

template class T1, class T2>

void copy(T1 a[], T2 b[], int n) { for(int i=0; i<n; i++) a[i]=b[i]; }

В этой форме существует поэлементное преобразование. Оно обычно соответствует и наиболее безопасному преобразованию.

4.3.2 Совпадение сигнатуры и перегрузка

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

template <class T>

void swap(T &x, T& y) { T temp; temp=x; x=y; y=temp; }

Шаблон функции используется, чтобы создавать для любого вызова соответствующую функцию, недвусмысленно соответствующую параметрам.

int i.j;

char str1[100],str2[100];

complex c1,c2;

swap(i,j); // i, j -int допустимо

swap(с1,с2); // с1, с2 -complex допустимо

swap(str1[50],str2[33]); // обе -char допустимо

swap(str1,str2); // допустимо

swap(i, chj); // i, -int, ch -char не допустимо

Сделаем так, чтобы swap работала для строк, представляемых как символьные массивы; для этого опишем следующий специальный случай.

void swap (char *s1, char *s2) {

int len=(strlen(s1) >= strlen(s2)) ? strlen(s1): strlen(s2);

char *temp=new char [len+1]; strcpy(temp,s1); strcpy(s1,s2); strcpy(s2,temp); delete temp;

}

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

Алгоритм выбора функции перегрузки следующий:

- Найти точное соответствие функции не-шаблону.

- Найти точное соответствие, использующее шаблон функции

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

Статические члены

Статические члены не универсальны, а специфичны для каждой реализации.

template <class T>

class foo {

public:

static int count;

};

template <class T> int foo::count=0;

....

foo <int> a;

foo <double> b;

Определены статические переменные foo<int>::count и foo<double>::cout.

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

· Это объявление функции, предваряемое спецификацией шаблона. Спецификация шаблона состоит из ключевого слова template, за которым следует список параметров, заключенный в угловые скобки <>.

· Имеют параметры типа, которые обозначаются ключевым словом class, за которым следует идентификатор. Ключевое слово class в контексте шаблонов означает любой тип, а не только класс. Идентификатор служит для замещения имени типа. Может быть более одного параметра типа.

· Шаблоны функций автоматически расширяются транслятором до полного описания функции так, как это необходимо.

// Объявление: максимум из двух значений типа T

template <class T> const T &Max(const T&,const T&);

template <class T> const T & // Определение

Max(const T &a,const T &b) { return a>b ? a : b; }

int i,j; float a,b;

int k=Max(i,j); // Вызов Max(int,int)

int c=Max(a,b); // Вызов Max(float,float)

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

· Шаблоны функций могут быть перегружены другими функциями.

template <class T> const T & Max(const T &,const T &);

template <class T> const T & Max(const T *,int);

int Max(int (*)(int),int (*)(int));

· Для определенных типов шаблоны функций могут быть перекрыты для того, чтобы выполнять (или не выполнять) какие-либо действия, которые функции-шаблоны не выполняют (или выполняют).

const char * &Max(const char * &c, const char * &d) {/*

Выполняет что-либо, специфичное для char * }

Шаблоны функций-членов

· Определяются вне объявлений классов, к которым они принадлежат, при помощи спецификации template

template<class T> void Stack<T>::Push(const T &el) {

if(top==size-1) error(“stack overflow”);

else v[++top]=el;

}

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

void Satck<char*>::Push(const char*& cpr){/*выполнить нечто особенное */}

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

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

template <class T >

class описание класса

Как и для функций, в описателе template может быть задано несколько параметров. В самом описание класса имена параметров используются как имена типов данных, типов параметров функций и типов значений, возвращаемых функциями.

В качестве примера приведем описание класса stack, предназначенного для построения стеков фиксированного максимального размера с элементами произвольного типа.

enum BOOLEAN ( FALSE, TRUE );

template <class Type >

class stack

{ private:

enum ( EMPTY = -1 );

Type* s; /* Указатель на массив стека */

int max_len; /* Максимальная длина стека */

int top; /* Индекс элемента в вершине стека */

public:

stack ( ) : max_len ( 100 ) /* конструктор без параметров */

{ s = new Type [ 100 ]; top = EMPTY; }

stack ( int size ) : max_len( size ) /* Второй конструктор */

{ s = new Type [ size ]; top = EMPTY; }

~stack ( ) { delete [ ] s; } /* Деструктор */

void reset ( ) { top = EMPTY; } /* Очистить стек */

void push ( Type c ) { s [ ++top ] = c; }

Type pop ( ) { return (s [top—] }

Type top_of ( ) { return ( s [top ] }

BOOLEAN empty ( ) { return BOOLEAN ( top == EMPTY ) }

BOOLEAN full ( ) { return BOOLEAN ( top == max_len ) }

};

Следует отметить, что в этом примере с целью сокращения исходного текста не предусмотрен контроль выхода за пределы стека в методах push и pop.

Чтобы создать экземпляр параметризованного объектного типа, нужно уточнить имя типа значением параметра в угловых скобках:

stack < int > stack_of_int (50); /* Стек на 50 элементов типа int */

stack < myClass > stmc (20); /* Стек на 20 элементов типа myClass */

В приведенном примере все компоненты-функции определены в описании класса. Когда полное определение функции-члена класса задается вне описания класса, оно должно уточняться описателем template. Например, если бы метод top_of был определен вне описания класса, определение имело бы вид:

template < class Type >

Type top_of ( ) { return s [ top ];}

Отметим некоторые специфические черты описаний параметризованных классов.

Если в параметризованном классе определены friend-функции, то когда такая функция не зависит от параметра, будет использоваться единственная friend-функция для всех значений параметра, а когда friend-функция зависит от параметра, будет использоваться своя friend-функция для каждого значения параметра.

Если в параметризованном классе имеются статические (static) компоненты, то для каждого значения параметра будет использоваться свой экземпляр статической компоненты.

Рассмотри пример реализации шаблона stack

template <class TYPE>

class stack {

enum { EMPTY=-1};

TYPE *s;

int len, top;

public:

stack: len(1000) { s=new TYPE[1000]; top=EMPTY; }

stack(int size): len(size) { s=new TYPE[size]; top=EMPTY; }

~stack() { delete s; }

void reset() { top=EMPTY; }

void push(TYPE c) { s[++top]=c; }

TYPE pop() { return s[top--]; }

TYPE top_of() { return s[top]; }

boolean empty() { return boolean(top==EMPTY); }

boolean full() { return boolean(top==len); }

};

Синтаксис объявления класса предваряется

template <class идентификатор>

где идентификатор - параметр шаблона, который, по существу, заменяет произвольный тип. Через определение класса аргумент шаблона может использоваться как имя типа. Этот аргумент создается при реализации экземпляра в фактических объявлениях. Вот пример использующего его объявления стека:

stack <char> stk_ch; // stack из 1000 элементов char

stack <char *> stk_str(200); // stack из 200 элементов char *

stack <complex> stk_cmplx(100); // stack из 100 элементов complex

Этот механизм спасает от трудоемкого переписывания объявлений классов в том случае, когда единственным отличием является объявление типа. Эта схема - альтернатива к использованию void * в качестве универсального типа указателя.

При обработки такого типа код всегда должен содержать угловые скобки в виде части объявления.

// Реверсирование последовательности char * представляемых строками

void reverse(char *str[], int n) {

stack <char *>stk(n);

for(int i=0; i<n; i++) stk.push(str[i]);

for(i=0; i<n; i++) str[i]=stk.pop();

}

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

void init(complex c[], stack <complex> stk, int n) {

for(int i=0; i<n; i++) stk.push(c[i]);

}

Функции-члены, объявленные и определенные внутри класса, как и ранее, обычно являются inline. При внешнем определении должно использоваться полное объявление в угловых скобках. Так top_of() при определении вне класса шаблона, будет записано как

template <class TYPE> TYPE stack<TYPE>::top_of() { return s[top]; }

Дружественность

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

template <class T>

class matrix {

friend void foo_bar(); // универсальная

friend vect <T> product(vect <T> v); // создается экземпляр

};

Аргументы шаблона класса

Как классы, так и функции могут иметь несколько аргументов шаблона класса. Напишем функцию, которая преобразует значение одного типа к другому типу, при условии, что первый тип, по крайней мере, такой же длины как и второй.

template <class T1,class T2 >

boolean coerce(T1& x, T2 y) {

if(sizeof(x) >= sizeof(y)) x=(T1)y;

else false;

return true;

}

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

Другие параметры шаблона включают константные выражения, имена функций и символьные строки.

template <int n, class T>

class array {

public:

T a[n];

};

array <50,double> x,y;

x=y; // должно работать эффективно

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

Параметризация класса vect

Класс vect часто подлежит параметризации.

template <class T >

class vect {

T *p; // базовый указатель

int size; // число элементов

public:

vect(); // создает массив размерности 10

vect(int n); // создает массив размерности n

vect(const vect &v); // инициализация от vect

vect(const T a[],int n); // инициализация от массива

~vect() { delete p; }

T& operator [](int i); // элемент, проверенный на соответствие границ

vect& operator = (const vect& v);

void print();

};

Определение функций-членов в контексте файла включает метку разрешения имя класса <T>.

template <class T>

vect<T>::vect() { size=10; p=new T[size]; }

template <class T>

vect <T>::vect(int n) { size=n; p=new T[size]; }

template <class T>

vect<T>::vect(const T a[], int n) { size=n; p=new T [size]; for(int i=0; i<size; i++) p[i]=a[i]; }

template <class T>

vect<T>::vect(const vect<T>&v) {

size=v.size; p=new T[size]; for(int i=0; i<size; i++) p[i]=v.p[i]; }

template <class T>

T& vect<T>::operator[](int i) { return p[i]; }

template <class T>

vect<T>& vect<T>::operator = (const vect<T> &v) {

int s=(size < v.size) ? size : v.size;

for(int i=0; i<s; i++) p[i]=v.p[i];

return *this;

}

Код пользователя почти также прост, как и для не параметризованных объявлений. Чтобы использовать эти объявления, в угловых скобках добавляется только специфический тип, который создает экземпляр шаблона. Этот тип может быть встроенным типом, таким как int или типом, определяемом пользователем. Эти шаблоны используются в следующем коде.

main() {

vect<int> v(8);

vect <float> v1(10);

}

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

· Шаблоны классов - это объявления классов, предваряемые спецификацией template.

· Шаблоны классов автоматически расширяются компилятором до полных определений классов так, как это необходимо.

· Не могут быть вложены в другие классы (в отличие от других классов).

//Объявить класс Stack, который представляет собой стек для любых типов.

template <class T> class Stack {

T *v; // указатель на некоторый тип T

int size,top;

public:

Stack(int s); ~Stack(); // и т.д.

};

Stack <int> i; // Стек для int

Stack <char*> cp; // Стек для char*

· Шаблоны классов могут иметь нетипированные (или только нетипированные) параметры. Значения, указанные для этих параметров, должны быть константными выражениями.

// Передать размер как параметр шаблона

template <class T,int size> class Stack {

T v[size]; // Массив элементов типа T.

int top;

public:

Stack():top(-1) {}//...

};

Stack <int,20> tiny;

Stack <int,500> huge;

Хотя стеки tiny и huge и хранят тип int, но все же это различные типы, поскольку они имеют разный размер стека. Это можно проиллюстрировать тем, что указатель Stack<int,20> - это не то же самое, что указатель на Stack<int,500>.

Stack<int,20> *ps20=&tiny; // Правильно

Stack<int,500> *ps500=&tiny; // Ошибка

· Шаблоны классов могут быть порождены как от нешаблонных классов, так и от классов шаблонов. А также могут порождать как нешаблонные классы, так и классы-шаблоны. Когда от класса-шаблона порождается нешаблонный класс, всем параметрам класса-шаблона должны быть присвоены некоторые “реальные” значения. В примере это int.

class A { /*...*/ }

template <class T> class B: public A { /*...*/ }

template <class T> class C: public B { /*...*/ }

class D: public C <int> { /*...*/ }

· Шаблоны классов для определенных типов могут быть перекрыты для того, чтобы выполнять (или не выполнять) какие-либо действия, которые шаблоны классов не выполняют (или выполняют).

// Объявим свой собственный стек для char*

class Stack<char*> {

char * *v; // указатель на char*

int size,top; //...

};

· Шаблоны классов могут также быть классами-структурами или классами-объединениями.

Статические данные-члены

· Статические данные-члены разделяются всеми объектами класса для каждого конкретного экземпляра класса-шаблона

· Статические данные-члены определяются в области видимости файла (как и все статические поля), когда определение предваряется спецификацией template

template <class T> class C {

static int i; // Обычное статическое поле

static T t; // Параметризованное

};

template<class T> int C<T>::i; // Определить в области видимости файла

template<class T> T C<T>::t;

С<char> c; // Имеет int C::i и char C::t;

C<float> f; // Имеет int C::i и float C::t;