16.8. Шаблоны классов и модель компиляции A
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 );
// ...
}
Конкретизация шаблона класса некоторым типом не приводит к автоматической конкретизации всех его членов тем же типом. Член конкретизируется только при использовании в таком контексте, где необходимо его определение (т.е. вложенный тип употреблен так, что требуется его полное определение; вызвана функция-член или взят ее адрес; имеется обращение к значению статического члена).
Конкретизация функций-членов и статических членов шаблонов класса поднимает те же вопросы, которые мы уже обсуждали для шаблонов функций в разделе 10.5. Чтобы компилятор мог конкретизировать функцию-член или статический член шаблона класса, должно ли определение члена быть видимым в момент конкретизации? Например, должно ли определение функции-члена add() появиться до ее конкретизации типом int в main()? Следует ли помещать определения функций-членов и статических членов шаблонов класса в заголовочные файлы (как мы поступаем с определениями встроенных функций), которые включаются всюду, где применяются их конкретизированные экземпляры? Или конкретизации определения шаблона достаточно для того, чтобы этими членами можно было пользоваться, так что определения членов можно оставлять в файлах с исходными текстами (где обычно располагаются определения невстроенных функций-членов и статических членов)?
Для ответа на эти вопросы нам придется вспомнить модель компиляции шаблонов в C++, где формулируются требования к организации программы, в которой определяются и употребляются шаблоны. Обе модели (с включением и с разделением), описанные в разделе 10.5, в полной мере применимы и к определениям функций-членов и статических данных-членов шаблонов классов. В оставшейся части этого раздела описываются обе модели и объясняется их использование с определениями членов.
16.8.1. Модель компиляции с включением
В этой модели мы включаем определения функций-членов и статических членов шаблонов классов в каждый файл, где они конкретизируются. Для встроенных функций-членов, определенных в теле шаблона, это происходит автоматически. В противном случае такое определение следует поместить в один заголовочный файл с определением шаблона класса. Именно этой моделью мы и пользуемся в настоящей книге. Например, определения шаблонов Queue и QueueItem, как и их функций-членов и статических членов, находятся в заголовочном файле Queue.h.
Подобное размещение не лишено недостатков: определения функций-членов могут быть довольно большими и содержать детали реализации, которые неинтересны пользователям или должны быть скрыты от них. Кроме того, многократная компиляция одного определения шаблона при обработке разных файлов увеличивает общее время компиляции программы. Описанная модель (если она доступна) позволяет отделить интерфейс шаблона от реализации (т.е. от определений функций-членов и статических данных-членов).