Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

Шаблоны-члены

Шаблон функции или класса может быть членом обычного класса или шаблона класса. Определение шаблона-члена похоже на определение шаблона: ему предшествует ключевое слово template, за которым идет список параметров:

template < class T>

class Queue {

private:

// шаблон класса-члена

template < class Type>

class CL

{

Type member;

T mem;

};

//...

public:

// шаблон функции-члена

template < class Iter>

void assign( Iter first, Iter last )

{

while (! is_empty() )

    remove(); // вызывается Queue< T>:: remove()

 

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

     add( *first ); // вызывается Queue< T>:: add( const T & )

}

}

(Отметим, что шаблоны-члены не поддерживаются компиляторами, написанными до принятия стандарта C++. Эта возможность была добавлена в язык для поддержки реализации абстрактных контейнерных типов, представленных в главе 6.)

Объявление шаблона-члена имеет собственные параметры. Например, у шаблона класса CL есть параметр Type, а у шаблона функции assign() – параметр Iter. Помимо этого, в определении шаблона-члена могут использоваться параметры объемлющего шаблона класса. Например, у шаблона CL есть член типа T, представляющего параметр включающего шаблона Queue.

Объявление шаблона-члена в шаблоне класса Queue означает, что конкретизация Queue потенциально может содержать бесконечное число различных вложенных классов CL функций-членов assign(). Так, конкретизированный экземпляр Queue< int> включает вложенные типы:

Queue< int>:: CL< char>

Queue< int>:: CL< string>

и вложенные функции:

void Queue< int>:: assign( int *, int * )

void Queue< int>:: assign( vector< int>:: iterator,

                    vector< int>:: iterator )

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

Шаблон-член конкретизируется при его использовании в программе. Например, assign() конкретизируется в момент обращения к ней из main():

int main()

{

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

Queue< int> qi;

 

// конкретизация Queue< int>:: assign( int *, int * )

int ai[4] = { 0, 3, 6, 9 };

qi.assign( ai, ai + 4 );

 

// конкретизация Queue< int>:: assign( vector< int>:: iterator,

//                                vector< int>:: iterator )

vector< int> vi( ai, ai + 4 );

qi.assign( vi.begin(), vi.end() );

}

Шаблон функции assign(), являющийся членом шаблона класса Queue, иллюстрирует необходимость применения шаблонов-членов для поддержки контейнерных типов. Предположим, имеется очередь типа Queue< int>, в которую нужно поместить содержимое любого другого контейнера (списка, вектора или обычного массива), причем его элементы имеют либо тип int (т.е. тот же, что у элементов очереди), либо приводимый к типу int. Шаблон-член assign()позволяет это сделать. Поскольку может быть использован любой контейнерный тип, то интерфейс assign() программируется в расчете на употребление итераторов; в результате реализация оказывается не зависящей от фактического типа, на который итераторы указывают.

В функции main() шаблон-член assign() сначала конкретизируется типом int*, что позволяет поместить в qi содержимое массива элементов типа int. Затем шаблон-член конкретизируется типом vector< int>:: iterator – это дает возможность поместить в очередь qi содержимое вектора элементов типа int. Контейнер, содержимое которого помещается в очередь, не обязательно должен состоять из элементов типа int. Разрешен любой тип, который приводится к int. Чтобы понять, почему это так, еще раз посмотрим на определение assign():

template < class Iter>

void assign( Iter first, Iter last )

{

// удалить все элементы из очереди

 

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

add( *first );

}

Вызываемая из assign() функция add() – это функция-член Queue< Type>:: add(). Если Queue конкретизируется типом int, то у add() будет следующий прототип:

void Queue< int>:: add( const int & val );

Аргумент *first должен иметь тип int либо тип, которым можно инициализировать параметр-ссылку на const int. Преобразования типов допустимы. Например, если воспользоваться классом SmallInt из раздела 15.9, то содержимое контейнера, в котором хранятся элементы типа SmallInt, с помощью шаблона-члена assign() помещается в очередь типа Queue< int>. Это возможно потому, что в классе SmallInt имеется конвертер для приведения SmallInt к int:

class SmallInt {

public:

SmallInt( int ival = 0 ): value( ival ) { }

 

// конвертер: SmallInt ==> int

operator int() { return value; }

 

//...

private:

int value;

};

 

int main()

{

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

Queue< int> qi;

 

vector< SmallInt> vsi;

// заполнить вектор

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

// Queue< int>:: assign( vector< SmallInt>:: iterator,

//                vector< SmallInt>:: iterator )

qi.assign( vsi.begin(), vsi.end() );

 

list< int*> lpi;

// заполнить список

 

// ошибка при конкретизации шаблона-члена assign():

// нет преобразования из int* в int

qi.assign( lpi.begin(), lpi.end() );

}

Первая конкретизация assign() правильна, так как существует неявное преобразование из типа SmallInt в тип int и, следовательно, обращение к add() корректно. Вторая же конкретизация ошибочна: объект типа int* не может инициализировать ссылку на тип const int, поэтому вызвать функцию add() невозможно.

Для контейнерных типов из стандартной библиотеки C++ имеется функция assign(), которая ведет себя так же, как функция-шаблон assign() для нашего класса Queue.

Любую функцию-член можно задать в виде шаблона. Это относится, в частности, к конструктору. Например, для шаблона класса Queue его можно определить следующим образом:

template < class T>

class Queue {

//...

public:

// шаблон-член конструктора

template < class Iter>

Queue( Iter first, Iter last )

  : front( 0 ), back( 0 )

{

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

     add( * first );

}

};

Такой конструктор позволяет инициализировать очередь содержимым другого контейнера. У контейнерных типов из стандартной библиотеки C++ также есть предназначенные для этой цели конструкторы в виде шаблонов-членов. Кстати, в первом (в данном разделе) определении функции main() использовался конструктор-шаблон для вектора:

vector< int> vi( ai, ai + 4 );

Это определение конкретизирует шаблон конструктора для контейнера vector< int> типом int*, что позволяет инициализировать вектор содержимым массива элементов типа int.

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

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>:: CL< Type>

{

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 )

{... }


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 349; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.301 с.)
Главная | Случайная страница | Обратная связь