Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
84
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

804

#include <iostream> #include "Queue.h"

int main() { Queue<int> qi;

//конкретизируются оба экземпляра

//ostream& operator<<(ostream &os, const Queue<int> &)

//ostream& operator<<(ostream &os, const QueueItem<int> &) cout << qi << endl;

int ival;

for ( ival = 0; ival < 10; ++ival ) qi.add( ival );

cout << qi << endl;

int err_cnt = 0;

for ( ival = 0; ival < 10; ++ival ) { int qval = qi.remove();

if ( ival != qval ) err_cnt++;

}

cout << qi << endl; if ( !err_cnt )

cout << "!! queue executed ok\n";

else cout << "?? queue errors: " << err_cnt << endl; return 0;

}

После компиляции и запуска программа выдает результат:

<>

<0 1 2 3 4 5 6 7 8 9 >

<>

!! queue executed ok

Упражнение 16.6

Пользуясь шаблоном класса Screen, определенным в упражнении 16.5, реализуйте операторы ввода и вывода (см. упражнение 15.6 из раздела 15.2) в виде шаблонов. Объясните, почему вы выбрали тот, а не иной способ объявления друзей класса Screen, добавленных в его шаблон.

16.5. Статические члены шаблонов класса

В шаблоне класса могут быть объявлены статические данные-члены. Каждый конкретизированный экземпляр имеет собственный набор таких членов. Рассмотрим операторы new() и delete() для шаблона QueueItem. В класс QueueItem нужно

static QueueItem<Type> *free_list;

добавить два статических члена:

static const unsigned QueueItem_chunk;

Модифицированное определение шаблона QueueItem выглядит так:

С++ для начинающих

805

 

 

#include <cstddef>

 

 

 

 

 

 

template <class Type>

 

 

 

class QueueItem {

 

 

 

// ...

 

 

 

private:

 

 

 

void *operator new( size_t );

 

 

 

void operator delete( void *, size_t );

 

 

 

// ...

 

 

 

static QueueItem *free_list;

 

 

 

static const unsigned QueueItem_chunk;

 

 

 

// ...

 

 

 

};

 

 

 

 

 

 

 

Операторы new() и delete() объявлены закрытыми, чтобы предотвратить создание

 

объектов типа QueueItem вызывающей программой: это разрешается только членам и

 

друзьям QueueItem (к примеру, шаблону Queue).

 

 

 

template <class Type> void*

 

 

 

 

 

 

QueueItem<Type>::operator new( size_t size )

 

 

 

{

 

 

 

QueueItem<Type> *p;

 

 

 

if ( ! free_list )

 

 

 

{

 

 

 

size_t chunk = QueueItem_chunk * size;

 

 

 

free_list = p =

 

 

 

reinterpret_cast< QueueItem<Type>* >

 

 

 

( new char[chunk] );

 

 

 

for ( ; p != &free_list[ QueueItem_chunk - 1 ]; ++p )

 

 

 

p->next = p + 1;

 

 

 

p->next = 0;

 

 

 

}

 

 

 

p = free_list;

 

 

 

free_list = free_list->next;

 

 

 

return p;

 

Оператор new() можно реализовать таким образом:

 

 

 

}

 

 

 

template <class Type>

 

 

 

 

 

 

 

 

 

void QueueItem<Type>::

 

 

 

operator delete( void *p, size_t )

 

 

 

{

 

 

 

static_cast< QueueItem<Type>* >( p )->next = free_list;

 

 

 

free_list = static_cast< QueueItem<Type>* > ( p );

 

А реализация оператора delete() выглядит так:

 

 

 

}

 

 

 

 

 

Теперь остается инициализировать статические члены free_list и QueueItem_chunk.

 

Вот шаблон для определения статических данных-членов:

 

С++ для начинающих

806

/* для каждой конкретизации QueueItem сгенерировать

* соответствующий free_list и инициализировать его нулем

*/

template <class T>

QueueItem<T> *QueueItem<T>::free_list = 0;

/* для каждой конкретизации QueueItem сгенерировать

* соответствующий QueueItem_chunk и инициализировать его значением 24 */

template <class T> const unsigned int

QueueItem<T>::QueueItem_chunk = 24;

Определение шаблона статического члена должно быть вынесено за пределы определения самого шаблона класса, которое начинается с ключевого слово template с последующим списком параметров <class T>. Имени статического члена предшествует префикс QueueItem<T>::, показывающий, что этот член принадлежит именно шаблону QueueItem. Определения таких членов помещаются в заголовочный файл Queue.h и должны включаться во все файлы, где производится их конкретизация. (В разделе 16.8 мы объясним, почему решили делать именно так, и затронем другие вопросы, касающиеся модели компиляции шаблонов.)

Статический член конкретизируется по шаблону только в том случае, когда реально используется в программе. Сам такой член тоже является шаблоном. Определение шаблона для него не приводит к выделению памяти: она выделяется только для конкретизированного экземпляра статического члена. Каждая подобная конкретизация соответствует конкретизации шаблона класса. Таким образом, обращение к экземпляру

статического члена всегда производится через некоторый конкретизированный экземпляр

// ошибка: QueueItem - это не реальный конкретизированный экземпляр int ival0 = QueueItem::QueueItem_chunk;

int ival1 = QueueItem<string>::QueueItem_chunk; // правильно

класса:

int ival2 = QueueItem<int>::QueueItem_chunk; // правильно

Упражнение 16.7

Реализуйте определенные в разделе 15.8 операторы new() и delete() и относящиеся к ним статические члены screenChunk и freeStore для шаблона класса Screen, построенного в упражнении 16.6.

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

Шаблон класса QueueItem применяется только как вспомогательное средство для реализации Queue. Чтобы запретить любое другое использование, в шаблоне QueueItem имеется закрытый конструктор, позволяющий создавать объекты этого класса исключительно функциям-членам класса Queue, объявленным друзьями QueueItem. Хотя шаблон QueueItem виден во всей программе, создать объекты этого класса или обратиться к его членам можно только при посредстве функций-членов Queue.

С++ для начинающих

807

Альтернативный подход к реализации состоит в том, чтобы вложить определение шаблона класса 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):

С++ для начинающих

808

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; // правильно

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