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

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

798

!! queue executed ok

Упражнение 16.5

Используя шаблон класса Screen, определенный в разделе 16.2, реализуйте функции- члены Screen (см. разделы 13.3, 13.4 и 13.6) в виде функций-членов шаблона.

16.4. Объявления друзей в шаблонах классов

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

799

∙ обычный (не шаблонный) дружественный класс или дружественная функция. В следующем примере функция foo(), функция-член bar() и класс foobar

class Foo { void bar();

};

template <class T> class QueueItem {

friend class foobar; friend void foo(); friend void Foo::bar(); // ...

объявлены друзьями всех конкретизаций шаблона QueueItem:

};

Ни класс foobar, ни функцию foo() не обязательно объявлять или определять в

глобальной области видимости перед объявлением их друзьями шаблона

QueueItem.

Однако перед тем как объявить другом какой-либо из членов класса Foo, необходимо определить его. Напомним, что член класса может быть введен в

область видимости только через определение объемлющего класса. QueueItem не может ссылаться на Foo::bar(), пока не будет найдено определение Foo;

связанный дружественный шаблон класса или функции. В следующем примере определено взаимно однозначное соответствие между классами, конкретизированными по шаблону QueueItem, и их друзьями также конкретизациями шаблонов. Для каждого класса, конкретизированного по шаблону QueueItem, ассоциированные конкретизации foobar, foo() и

template <class Type> class foobar { ... };

template <class Type>

void foo( QueueItem<Type> );

template <class Type> class Queue {

void bar(); // ...

};

template <class Type> class QueueItem {

friend class foobar<Type>;

friend void foo<Type>( QueueItem<Type> ); friend void Queue<Type>::bar();

// ...

Queue::bar() являются друзьями.

};

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

800

Прежде чем шаблон класса можно будет использовать в объявлениях друзей, он сам должен быть объявлен или определен. В нашем примере шаблоны классов foobar и Queue, а также шаблон функции foo() следует объявить до того, как они объявлены друзьями в QueueItem.

Синтаксис, использованный для объявления foo() другом, может показаться странным:

friend void foo<Type>( QueueItem<Type> );

За именем функции следует список явных аргументов шаблона: foo<Type>. Такой синтаксис показывает, что в качестве друга объявляется конкретизированный шаблон функции foo(). Если бы список явных аргументов был опущен:

friend void foo( QueueItem<Type> );

то компилятор интерпретировал бы объявление как относящееся к обычной функции (а не к шаблону), у которой тип параметра это экземпляр шаблона QueueItem. Как отмечалось в разделе 10.6, шаблон функции и одноименная обычная функция могут сосуществовать, и присутствие объявления такого

шаблона перед определением класса QueueItem не вынуждает компилятор соотнести объявление друга именно с ним. Для того, чтобы соотнесение было верным, в конкретизированном шаблоне функции необходимо указать список явных аргументов;

несвязанный дружественный шаблон класса или функции. В следующем примере имеется отображение один-ко-многим между конкретизациями шаблона класса QueueItem и его друзьями. Для каждой конкретизации типа QueueItem все

template <class Type> class QueueItem {

template <class T> friend class foobar;

template <class T>

friend void foo( QueueItem<T> );

template <class T>

friend class Queue<T>::bar();

// ...

конкретизации foobar, foo() и Queue<T>::bar() являются друзьями:

};

Следует отметить, что этот вид объявлений друзей в шаблоне класса не поддерживается компиляторами, написанными до принятия стандарта C++.

16.4.1. Объявления друзей в шаблонах Queue и QueueItem

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

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

801

секцию шаблона. Теперь класс Queue необходимо объявить другом QueueItem, чтобы можно было создавать и манипулировать объектами последнего.

Существует два способа объявить шаблон класса другом. Первый заключается в том,

template <class Type> class QueueItem {

//любой экземпляр Queue является другом

//любого экземпляра QueueItem

template <class T> friend class Queue;

чтобы объявить любой экземпляр Queue другом любого экземпляра QueueItem:

};

Однако нет смысла объявлять, например, класс Queue, конкретизированный типом string, другом QueueItem, конкретизированного типом complex<double>.

Queue<string> должен быть другом только для класса QueueItem<string>. Таким образом, нам нужно взаимно однозначное соответствие между экземплярами Queue и QueueItem, конкретизированными одинаковыми типами. Чтобы добиться этого,

template <class Type> class QueueItem {

//для любого экземпляра QueueItem другом является

//только конкретизированный тем же типом экземпляр Queue friend class Queue<Type>;

//...

применим второй метод объявления друзей:

};

Данное объявление говорит о том, что для любой конкретизации QueueItem некоторым типом экземпляр Queue, конкретизированный тем же типом, является другом. Так, экземпляр Queue, конкретизированный типом int, будет другом экземпляра QueueItem, тоже конкретизированного типом int. Но для экземпляров QueueItem, конкретизированных типами complex<double> или string, этот экземпляр Queue другом не будет.

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

// как задать аргумент типа Queue?

ему необходим доступ к закрытым членам класса. Какой же будет его сигнатура? ostream& operator<<( ostream &, ??? );

Поскольку Queue это шаблон класса, то в имени конкретизированного экземпляра должен быть задан полный список аргументов:

ostream& operator<<( ostream &, const Queue<int> & );

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

802

Так мы определили оператор вывода для класса, конкретизированного из шаблона Queue типом int. Но что, если Queue это очередь элементов типа string?

ostream& operator<<( ostream &, const Queue<string> & );

Вместо того чтобы явно определять нужный оператор вывода по мере необходимости, желательно сразу определить общий оператор, который будет работать для любой конкретизации Queue. Например:

ostream& operator<<( ostream &, const Queue<Type> & );

template <class Type> ostream&

Однако из этого перегруженного оператора вывода придется сделать шаблон функции: operator<<( ostream &, const Queue<Type> & );

Теперь всякий раз, когда оператору ostream передается конкретизированный экземпляр Queue, конкретизируется и вызывается шаблон функции. Вот одна из возможных

template <class Type>

ostream& operator<<( ostream &os, const Queue<Type> &q )

{

os << "< "; QueueItem<Type> *p;

for ( p = q.front; p; p = p->next ) os << *p << " ";

os << " >"; return os;

реализаций оператора вывода в виде такого шаблона:

}

Если очередь объектов типа int содержит значения 3, 5, 8, 13, то распечатка ее

содержимого с помощью такого оператора дает

< 3 5 8 13 >

Обратите внимание, что оператор вывода обращается к закрытому члену front класса

template <class Type> class Queue {

friend ostream&

operator<<( ostream &, const Queue<Type> & ); // ...

Queue. Поэтому оператор необходимо объявить другом Queue:

};

Здесь, как и при объявлении друга в шаблоне класса Queue, создается взаимно однозначное соответствие между конкретизациями Queue и оператора operator<<().

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

803

Распечатка элементов Queue производится оператором вывода operator<<() класса

QueueItem:

os << *p;

Этот оператор также должен быть реализован в виде шаблона функции; тогда можно

template <class Type>

ostream& operator<<( ostream &os, const QueueItem<Type> &qi )

{

os << qi.item; return os;

быть уверенным, что в нужный момент будет конкретизирован подходящий экземпляр:

}

Поскольку здесь имеется обращение к закрытому члену item класса QueueItem, оператор

template <class Type> class QueueItem {

friend class Queue<Type>; friend ostream&

operator<<( ostream &, const QueueItem<Type> & ); // ...

следует объявить другом шаблона QueueItem. Это делается следующим образом:

};

Оператор вывода класса QueueItem полагается на то, что item умеет распечатывать себя:

os << qi.item;

Это порождает тонкую зависимость типов при конкретизации Queue. Любой определенный пользователем и связанный с Queue класс, содержимое которого нужно распечатывать, должен предоставлять оператор вывода. В языке нет механизма, с

помощью которого можно было бы задать такую зависимость в определении самого шаблона Queue. Но если оператор вывода не определен для типа, с которым конкретизируется данный шаблон, и делается попытка вывести содержимое конкретизированного экземпляра, то в том месте, где используется отсутствующий оператор вывода, компилятор выдает сообщение об ошибке. Шаблон Queue можно конкретизировать типом, не имеющим оператора вывода, – при условии, что не будет попытки распечатать содержимое очереди.

Следующая программа демонстрирует конкретизацию и использование функций-друзей шаблонов классов Queue и QueueItem: