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

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

Шаблон класса 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 не означает автоматической конкретизации и класса QueueItemint. Члены front и back - это указатели на QueueItemint, а если объявлены только указатели на некоторый тип, то конкретизировать соответствующий класс не обязательно, хотя QueueItem вложен в шаблон класса Queue. QueueItemint конкретизируется только тогда, когда указатели front или back разыменовываются в функциях-членах класса Queueint.

Внутри шаблона класса можно также объявлять перечисления и определять типы (с помощью 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 объявляется перечисление с двумя элементами, которые инициализируются значением параметра шаблона. Например, объявление

Bufferint, 512 small_buf;

устанавливает Buf_size в 512, а last - в 511. Аналогично

Bufferint, 1024 medium_buf;

устанавливает Buf_size в 1024, а last - в 1023.

Открытый вложенный тип разрешается использовать и вне определения объемлющего класса. Однако вызывающая программа может ссылаться лишь на конкретизированные экземпляры подобного типа (или элементов вложенного перечисления). В таком случае имени вложенного типа должно предшествовать имя конкретизированного шаблона класса:

// ошибка: какая конкретизация Buffer?

Buffer::Buf_vals bfv0;

Bufferint,512::Buf_vals bfv1; // правильно

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

template class T class Q {

public:

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

QA status;

// ...

};

#include iostream

int main() {

Qdouble qd;

Qint qi;

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

qd.status = Qdouble ::empty; // правильно

int val1 = Qdouble ::empty;

int val2 = Qint ::empty;

if ( val1 != val2 )

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

return 0;

}

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

Упражнение 16.8

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