Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

template <class T> class Q { public:

enum QA { empty, full }; // не зависит от параметров QA status;

// ...

};

#include <iostream>

int main() { Q<double> qd; Q<int> qi;

qd.status = Q::empty; // ошибка: какая конкретизация Q?

qd.status = Q<double>::empty; // правильно

int val1 = Q<double>::empty; int val2 = Q<int>::empty; if ( val1 != val2 )

cerr << "ошибка реализации!" << endl; return 0;

}

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

Упражнение 16.8

Определите класс List и вложенный в него ListItem из раздела 13.10 как шаблоны. Реализуйте аналогичные определения для ассоциированных членов класса.

16.7. Шаблоны-члены

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

template <class T> class Queue { private:

// шаблон класса-члена template <class Type>

class CL

{

Type member; T mem;

};

//...

public:

//шаблон функции-члена template <class Iter>

void assign( Iter first, Iter last )

{

while ( ! is_empty() )

remove(); // вызывается Queue<T>::remove()

for ( ; first != last; ++first )

add( *first ); // вызывается Queue<T>::add( const T & )

}

}

(Отметим, что шаблоны-члены не поддерживаются компиляторами, написанными до принятия стандарта C++. Эта возможность была добавлена в язык для поддержки реализации абстрактных контейнерных типов, представленных в главе 6.)

Объявление шаблона-члена имеет собственные параметры. Например, у шаблона класса CL есть параметр Type, а у шаблона функции assign() – параметр Iter. Помимо этого, в определении шаблона-члена могут использоваться параметры объемлющего шаблона класса. Например, у шаблона CL есть член типа T, представляющего параметр включающего шаблона Queue.

Объявление шаблона-члена в шаблоне класса Queue означает, что конкретизация Queue потенциально может содержать бесконечное число различных вложенных классов CL функций-членов assign(). Так, конкретизированный экземпляр Queue<int> включает

Queue<int>::CL<char

>

вложенные типы:

Queue<int>::CL<string>

void Queue<int>::assign( int *, int * ) void

Queue<int>::assign( vector<int>::it erator,

и вложенные функции:

vector<int>::iterator )

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

друзья Queue могут ссылаться на его конкретизации. С другой стороны, шаблон функции assign() объявлен открытым членом и, значит, доступен во всей программе.

Шаблон-член конкретизируется при его использовании в программе. Например,

int main()

{

//конкретизация Queue<int> Queue<int> qi;

//конкретизация Queue<int>::assign( int *, int * ) int ai[4] = { 0, 3, 6, 9 };

qi.assign( ai, ai + 4 );

// конкретизация Queue<int>::assign( vector<int>::iterator,

//

vector<int>::iterator ) vector<int> vi( ai, ai + 4 ); qi.assign( vi.begin(), vi.end() );

assign() конкретизируется в момент обращения к ней из main():

}

Шаблон функции assign(), являющийся членом шаблона класса Queue, иллюстрирует необходимость применения шаблонов-членов для поддержки контейнерных типов. Предположим, имеется очередь типа Queue<int>, в которую нужно поместить содержимое любого другого контейнера (списка, вектора или обычного массива), причем его элементы имеют либо тип int (т.е. тот же, что у элементов очереди), либо приводимый к типу int. Шаблон-член assign()позволяет это сделать. Поскольку может быть использован любой контейнерный тип, то интерфейс assign() программируется в расчете на употребление итераторов; в результате реализация оказывается не зависящей от фактического типа, на который итераторы указывают.

В функции main() шаблон-член assign() сначала конкретизируется типом int*, что позволяет поместить в qi содержимое массива элементов типа int. Затем шаблон-член конкретизируется типом vector<int>::iterator – это дает возможность поместить в очередь qi содержимое вектора элементов типа int. Контейнер, содержимое которого помещается в очередь, не обязательно должен состоять из элементов типа int. Разрешен любой тип, который приводится к int. Чтобы понять, почему это так, еще раз посмотрим

template <class Iter>

void assign( Iter first, Iter last )

{

// удалить все элементы из очереди

for ( ; first != last; ++first ) add( *first );

на определение assign():

}

Вызываемая из assign() функция add() – это функция-член Queue<Type>::add(). Если Queue конкретизируется типом int, то у add() будет следующий прототип:

void Queue<int>::add( const int &val );

Аргумент *first должен иметь тип int либо тип, которым можно инициализировать параметр-ссылку на const int. Преобразования типов допустимы. Например, если воспользоваться классом SmallInt из раздела 15.9, то содержимое контейнера, в котором хранятся элементы типа SmallInt, с помощью шаблона-члена assign() помещается в очередь типа Queue<int>. Это возможно потому, что в классе SmallInt

class SmallInt { public:

SmallInt( int ival = 0 ) : value( ival ) { }

//конвертер: SmallInt ==> int operator int() { return value; }

//...

private:

int value;

};

int main()

{

// конкретизация Queue<int> Queue<int> qi;

vector<SmallInt> vsi;

//заполнить вектор

//конкретизация

//Queue<int>::assign( vector<SmallInt>::iterator,

vector<SmallInt>::iterator ) qi.assign( vsi.begin(), vsi.end() );

list<int*> lpi;

//заполнить список

//ошибка при конкретизации шаблона-члена assign():

//нет преобразования из int* в int qi.assign( lpi.begin(), lpi.end() );

имеется конвертер для приведения SmallInt к int:

}

Первая конкретизация assign() правильна, так как существует неявное преобразование из типа SmallInt в тип int и, следовательно, обращение к add() корректно. Вторая же конкретизация ошибочна: объект типа int* не может инициализировать ссылку на тип const int, поэтому вызвать функцию add() невозможно.

Для контейнерных типов из стандартной библиотеки C++ имеется функция assign(), которая ведет себя так же, как функция-шаблон assign() для нашего класса Queue.

Любую функцию-член можно задать в виде шаблона. Это относится, в частности, к конструктору. Например, для шаблона класса Queue его можно определить следующим образом:

template <class T> class Queue {

//...

public:

//шаблон-член конструктора template <class Iter>

Queue( Iter first, Iter last ) : front( 0 ), back( 0 )

{

for ( ; first != last; + +first )

add( * first );

}

};

Такой конструктор позволяет инициализировать очередь содержимым другого контейнера. У контейнерных типов из стандартной библиотеки C++ также есть предназначенные для этой цели конструкторы в виде шаблонов-членов. Кстати, в первом (в данном разделе) определении функции main() использовался конструктор-шаблон для вектора:

vector<int> vi( ai, ai + 4 );

Это определение конкретизирует шаблон конструктора для контейнера vector<int> типом int*, что позволяет инициализировать вектор содержимым массива элементов типа int.

Шаблон-член, как и обычные члены, может быть определен вне определения объемлющего класса или шаблона класса. Так, являющиеся членами шаблон класса CL или шаблон функции assign() могут быть следующим образом определены вне шаблона Queue: