Вложенные типы шаблонов классов
Шаблон класса QueueItem
применяется только как вспомогательное средство для реализации Queue. Чтобы запретить любое другое использование, в шаблоне QueueItem
имеется закрытый конструктор, позволяющий создавать объекты этого класса исключительно функциям-членам класса Queue, объявленным друзьями QueueItem. Хотя шаблон QueueItem
виден во всей программе, создать объекты этого класса или обратиться к его членам можно только при посредстве функций-членов Queue.
Альтернативный подход к реализации состоит в том, чтобы вложить определение шаблона класса QueueItem в закрытую секцию шаблона Queue. Поскольку QueueItem
является вложенным закрытым типом, он становится недоступным вызывающей программе, и обратиться к нему можно лишь из шаблона класса Queue и его друзей (например, оператора вывода). Если же сделать члены QueueItem
открытыми, то объявлять Queue другом QueueItem не понадобится.
Семантика исходной реализации при этом сохраняется, но отношение между шаблонами QueueItem и Queue
моделируется более элегантно.
Поскольку при любой конкретизации шаблона Queue
требуется конкретизировать тем же типом и QueueItem, то вложенный класс должен быть шаблоном. Вложенные классы шаблонов сами являются шаблонами классов, а параметры объемлющего шаблона можно использовать во вложенном:
template <class Type> class Queue: // ... private: class QueueItem { public: QueueItem( Type val ) : item( val ), next( 0 ) { ... } Type item; QueueItem *next; }; // поскольку QueueItem - вложенный тип, // а не шаблон, определенный вне Queue, // то аргумент шаблона <Type> после QueueItem можно опустить QueueItem *front, *back; // ... |
};
При каждой конкретизации Queue
создается также класс QueueItem с подходящим аргументом для Type. Между конкретизациями шаблонов QueueItem и Queue
имеется взаимно однозначное соответствие.
Вложенный в шаблон класс конкретизируется только в том случае, если он используется в контексте, где требуется полный тип класса. В разделе 16.2 мы упоминали, что конкретизация шаблона класса Queue
типом int не означает автоматической конкретизации и класса QueueItem<int>. Члены front и back – это указатели на QueueItem<int>, а если объявлены только указатели на некоторый тип, то конкретизировать соответствующий класс не обязательно, хотя QueueItem вложен в шаблон класса Queue. QueueItem<int>
конкретизируется только тогда, когда указатели front или back
разыменовываются в функциях-членах класса Queue<int>.
Внутри шаблона класса можно также объявлять перечисления и определять типы (с помощью typedef):
template <class Type, int size> class Buffer: public: enum Buf_vals { last = size-1, Buf_size }; typedef Type BufType; BufType array[ size ]; // ... |
Вместо того чтобы явно включать член Buf_size, в шаблоне класса Buffer
объявляется перечисление с двумя элементами, которые инициализируются значением параметра шаблона. Например, объявление
Buffer<int, 512> small_buf;
устанавливает Buf_size в 512, а last – в 511. Аналогично
Buffer<int, 1024> medium_buf;
устанавливает Buf_size в 1024, а last – в 1023.
Открытый вложенный тип разрешается использовать и вне определения объемлющего класса. Однако вызывающая программа может ссылаться лишь на конкретизированные экземпляры подобного типа (или элементов вложенного перечисления). В таком случае имени вложенного типа должно предшествовать имя конкретизированного шаблона класса:
// ошибка: какая конкретизация Buffer? Buffer::Buf_vals bfv0; |
Это правило применимо и тогда, когда во вложенном типе не используются параметры включающего шаблона:
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 как шаблоны. Реализуйте аналогичные определения для ассоциированных членов класса.