Язык программирования C++. Вводный курс

Вложенные типы шаблонов классов


Шаблон класса 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;

Buffer<int,512>::Buf_vals bfv1;  // правильно

Это правило применимо и тогда, когда во вложенном типе не используются параметры включающего шаблона:



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 как шаблоны. Реализуйте аналогичные определения для ассоциированных членов класса.


Содержание раздела