При конкретизации класса-шаблона Array параметр elemType заменяется на реальный тип при каждом использовании, как показано в примере:
#include iostream
#include "Array.h"
int main()
{
const int array_size = 4;
// elemType заменяется на int
Arrayint ia(array_size);
// elemType заменяется на double
Arraydouble da(array_size);
// elemType заменяется на char
Arraychar ca(array_size);
int ix;
for ( ix = 0; ix array_size; ++ix ) {
ia[ix] = ix;
da[ix] = ix * 1.75;
ca[ix] = ix + 'a';
}
for ( ix = 0; ix array_size; ++ix )
ia[ix]
"\tca: " ca[ix]
"\tda: " da[ix] endl;
return 0;
}
Здесь определены три экземпляра класса Array:
Arrayint ia(array_size);
Arraydouble da(array_size);
Arraychar ca(array_size);
Что делает компилятор, встретив такое объявление? Подставляет текст шаблона Array, заменяя параметр elemType на тот тип, который указан в каждом конкретном случае. Следовательно, объявления членов приобретают в первом случае такой вид:
// Arrayint ia(array_size);
int _size;
int *_ia;
Заметим, что это в точности соответствует определению массива IntArray.
Для оставшихся двух случаев мы получим следующий код:
// Arraydouble da(array_size);
int _size;
double *_ia;
// Arraychar ca(array_size);
int _size;
char *_ia;
Что происходит с функциями-членами? В них тоже тип-параметр elemType заменяется на реальный тип, однако компилятор не конкретизирует те функции, которые не вызываются в каком-либо месте программы. (Подробнее об этом в разделе 16.8.)
При выполнении программа этого примера выдаст следующий результат:
[ 0 ] ia: 0 ca: a da: 0
[ 1 ] ia: 1 ca: b da: 1.75
[ 2 ] ia: 2 ca: c da: 3.5
[ 3 ] ia: 3 ca: d da: 5.25
Механизм шаблонов можно использовать и в наследуемых классах. Вот как выглядит определение шаблона класса ArrayRC:
#include cassert
#include "Array.h"
template class elemType
class ArrayRC : public ArrayelemType {
public:
ArrayRC( int sz = DefaultArraySize )
: ArrayelemType( sz ) {}
ArrayRC( const ArrayRC r )
: ArrayelemType( r ) {}
ArrayRC( const elemType *ar, int sz )
: ArrayelemType( ar, sz ) {}
elemType ArrayRCelemType::operator[]( int ix )
{
assert( ix = 0 ix ArrayelemType::_size );
return _ia[ ix ];
}
private:
// ...
};
Подстановка реальных параметров вместо типа-параметра elemType происходит как в базовом, так и в производном классах. Определение
ArrayRCint ia_rc(10);
ведет себя точно так же, как определение IntArrayRC из предыдущего раздела. Изменим пример использования из предыдущего раздела. Прежде всего, чтобы оператор
// функцию swap() тоже следует сделать шаблоном
swap( ia1, 1, ia1.size() );
был допустимым, нам потребуется представить функцию swap() в виде шаблона.
#include "Array.h"
template class elemType
inline void
swap( ArrayelemType array, int i, int j )
{
elemType tmp = array[ i ];
array[ i ] = array[ j ];
array[ j ] = tmp;
}
При каждом вызове swap() генерируется подходящая конкретизация, которая зависит от типа массива. Вот как выглядит программа, использующая шаблоны Array и ArrayRC:
#include iostream
#include "Array.h"
#include "ArrayRC.h"
template class elemType
inline void
swap( ArrayelemType array, int i, int j )
{
elemType tmp = array[ i ];
array[ i ] = array[ j ];
array[ j ] = tmp;
}
int main()
{
Arrayint ia1;
ArrayRCint ia2;
cout "swap() with Arrayint ia1" endl;
int size = ia1.size();
swap( ia1, 1, size );