Выбрать главу

template class T

class Queue {

private:

template class Type class CL;

// ...

public:

template class Iter

void assign( Iter first, Iter last );

// ...

};

template class T template class Type

class Queue T ::CLType

{

Type member;

T mem;

};

template class T template class Iter

void Queue T ::assign( Iter first, Iter last )

{

while ( ! is_empty() )

remove();

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

add( *first );

}

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

template class T template class Iter

Первый список параметров шаблона template class T относится к шаблону класса Queue. Второй - к самому шаблону-члену assign(). Имена параметров не обязаны совпадать с теми, которые указаны внутри определения объемлющего шаблона класса. Приведенная инструкция по-прежнему определяет шаблон-член assign():

template class TT template class IterType

void Queue TT ::assign( IterType first, IterType last )

{ ... }

16.8. Шаблоны классов и модель компиляции A

Определение шаблона класса - это лишь предписание для построения бесконечного множества типов классов. Сам по себе шаблон не определяет никакого класса. Например, когда компилятор видит:

template class Type

class Queue { ... };

он только сохраняет внутреннее представление Queue. Позже, когда встречается реальное использование класса, конкретизированного по шаблону, скажем:

int main() {

Queue int *p_qi = new Queue int ;

}

компилятор конкретизирует тип класса Queueint, применяя сохраненное внутреннее представление определения шаблона Queue.

Шаблон конкретизируется только тогда, когда он употребляется в контексте, требующем полного определения класса. (Этот вопрос подробно обсуждался в разделе 16.2.) В примере выше класс Queue конкретизируется, потому что компилятор должен знать размер типа Queue, чтобы выделить нужный объем памяти для объекта, созданного оператором new.

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

// объявление шаблона класса

template class Type

class Queue;

Queueint * global_pi = 0; // правильно: определение класса не нужно

int main() {

// ошибка: необходима конкретизация

// определение шаблона класса должно быть видимо

Queue int *p_qi = new Queue int;

}

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

Функции-члены и статические данные-члены шаблонов классов, а также вложенные в них типы ведут себя почти так же, как сами шаблоны. Определения членов шаблона используются для порождения экземпляров членов в конкретизированном шаблоне. Если компилятор видит:

template class Type

void Queue Type::add( const Type &val )

{ ... }

он сохраняет внутреннее представление Queue Type::add(). Позже, когда в программе встречается фактическое употребление этой функции-члена, допустим через объект типа Queueint, компилятор конкретизирует Queueint::add(const int &), пользуясь таким представлением:

#include "Queue.h "

int main() {

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

Queue int *p_qi = new Queueint;

int ival;

// ...

// конкретизация Queue int::add( const int & )

p_qi- add( ival );

// ...

}

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