C++ для начинающих

2.5. Использование шаблонов

Наш класс IntArray служит хорошей альтернативой встроенному массиву целых чисел. Но в жизни могут потребоваться массивы для самых разных типов данных. Можно предположить, что единственным отличием массива элементов типа double от нашего является тип данных в объявлениях, весь остальной код совпадает буквально.
Для решения данной проблемы в С++ введен механизм шаблонов. В объявлениях классов и функций допускается использование параметризованных типов. Типы-параметры заменяются в процессе компиляции настоящими типами, встроенными или определенными пользователем. Мы можем создать шаблон класса Array, заменив в классе IntArray тип элементов int на обобщенный тип-параметр. Позже мы конкретизируем типы-параметры, подставляя вместо них реальные типы int, double и string. В результате появится способ использовать эти конкретизации так, как будто мы на самом деле определили три разных класса для этих трех типов данных.
Вот как может выглядеть шаблон класса Array:

template <class elemType>
class Array {
public:
   explicit Array( int sz = DefaultArraySize );
   Array( const elemType *ar, int sz );
   Array( const Array &iA );

virtual ~Array() { delete[] _ia; }
Array& operator=( const Array & );
int size() const { return _size; } virtual elemType& operator[]( int ix )
{ return _ia[ix]; }
virtual void sort( int,int );
virtual int find( const elemType& );
virtual elemType min();
virtual elemType max();
protected:
void init( const elemType*, int );
void swap( int, int );
static const int DefaultArraySize = 12;
int _size;
elemType *_ia;
};

Ключевое слово template говорит о том, что задается шаблон, параметры которого заключаются в угловые скобки (<>). В нашем случае имеется лишь один параметр elemType; ключевое слово class перед его именем сообщает, что этот параметр представляет собой тип.
При конкретизации класса-шаблона Array параметр elemType заменяется на реальный тип при каждом использовании, как показано в примере:

#include <iostream>
#include "Array.h"

int main()
{
const int array_size = 4; // elemType заменяется на int
Array<int> ia(array_size);
// elemType заменяется на double
Array<double> da(array_size); // elemType заменяется на char
Array<char> 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 )
cout << "[ " << ix << " ] ia: " << ia[ix]
<< "\tca: " << ca[ix]
<< "\tda: " << da[ix] << endl; return 0;
}

Здесь определены три экземпляра класса Array:

Array<int> ia(array_size);
Array<double> da(array_size);
Array<char> ca(array_size);

Что делает компилятор, встретив такое объявление? Подставляет текст шаблона Array, заменяя параметр elemType на тот тип, который указан в каждом конкретном случае. Следовательно, объявления членов приобретают в первом случае такой вид:

// Array<int> ia(array_size);
int _size;
int *_ia;

Заметим, что это в точности соответствует определению массива IntArray.
Для оставшихся двух случаев мы получим следующий код:

// Array<double> da(array_size);
int _size;
double *_ia;

// Array<char> 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 Array<elemType> {
public: ArrayRC( int sz = DefaultArraySize )
: Array<elemType>( sz ) {}
ArrayRC( const ArrayRC& r )
: Array<elemType>( r ) {}
ArrayRC( const elemType *ar, int sz )
: Array<elemType>( ar, sz ) {} elemType& ArrayRC<elemType>::operator[]( int ix )
{
assert( ix >= 0 && ix < Array<elemType>::_size );
return _ia[ ix ];
}
private:
// ...
};

Подстановка реальных параметров вместо типа-параметра elemType происходит как в базовом, так и в производном классах. Определение

ArrayRC<int> ia_rc(10);

ведет себя точно так же, как определение IntArrayRC из предыдущего раздела. Изменим пример использования из предыдущего раздела. Прежде всего, чтобы оператор
// функцию swap() тоже следует сделать шаблоном

swap( ia1, 1, ia1.size() );

был допустимым, нам потребуется представить функцию swap() в виде шаблона.

#include "Array.h"
template <class elemType>
inline void
swap( Array<elemType> &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( Array<elemType> &array, int i, int j )
{
elemType tmp = array[ i ];
array[ i ] = array[ j ];
array[ j ] = tmp;
}
int main()
{
Array<int> ia1;
ArrayRC<int> ia2;
cout << "swap() with Array<int> ia1" << endl;
int size = ia1.size();
swap( ia1, 1, size );
cout << "swap() with ArrayRC<int> ia2" << endl;
size = ia2.size();
swap( ia2, 1, size );
return 0;
}

Упражнение 2.13

Пусть мы имеем следующие объявления типов:

template<class elemType> class Array;
enum Status { ... };
typedef string *Pstring;

Есть ли ошибки в приведенных ниже описаниях объектов?

(a) Array< int*& > pri(1024);
(b) Array< Array<int> > aai(1024);
(c) Array< complex< double > > acd(1024);
(d) Array< Status > as(1024);
(e) Array< Pstring > aps(1024);

Упражнение 2.14

Перепишите следующее определение, сделав из него шаблон класса:

class example1 {
public:
   example1 (double min, double max);
   example1 (const double *array, int size);

double& operator[] (int index);
bool operator== (const example1&) const;
bool insert (const double*, int);
bool insert (double);
double min (double) const { return _min; };
double max (double) const { return _max; };
void min (double);
void max (double);
int count (double value) const;
private:
int size;
double *parray;
double _min;
double _max;
}

Упражнение 2.15

Имеется следующий шаблон класса:

template <class elemType> class Example2 {
public:
   explicit Example2 (elemType val=0) : _val(val) {};
   bool min(elemType value) { return _val < value; }
void value(elemType new_val) { _val = new_val; }
void print (ostream &os) { os << _val; } private:
elemType _val;
}
template <class elemType>
ostream& operator<<(ostream &os,const Example2<elemType> &ex)
{ ex.print(os); return os; }

Какие действия вызывают следующие инструкции?

(a) Example2<Array<int>*> ex1;
(b) ex1.min (&ex1);
(c) Example2<int> sa(1024),sb;
(d) sa = sb;
(e) Example2<string> exs("Walden");
(f) cout << "exs: " << exs << endl;

Упражнение 2.16

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

explicit Example2 (elemType val=0) : _val(val) {};

Однако не все типы могут быть инициализированы нулем (например, тип string), поэтому определение объекта

Example2<string> exs("Walden");

является правильным, а

Example2<string> exs2;

приведет к синтаксической ошибке . Также ошибочным будет вызов функции min(), если для данного типа не определена операция меньше. С++ не позволяет задать ограничения для типов, подставляемых в шаблоны. Как вы думаете, было бы полезным иметь такую возможность? Если да, попробуйте придумать синтаксис задания ограничений и перепишите в нем определение класса Example2. Если нет, поясните почему.

Упражнение 2.17

Как было показано в предыдущем упражнении, попытка использовать шаблон Example2 с типом, для которого не определена операция меньше, приведет к синтаксической ошибке. Однако ошибка проявится только тогда, когда в тексте компилируемой программы действительно встретится вызов функции min(), в противном случае компиляция пройдет успешно. Как вы считаете, оправдано ли такое поведение? Не лучше ли предупредить об ошибке сразу, при обработке описания шаблона? Поясните свое мнение.

Назад   Вперед
Содержание




Нет комментариев.



Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: