Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

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

792

 

Ptr< &iObj > pObj;

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

 

 

 

 

template <int hi, int wid> class Screen { ... };

const short shi = 40; const short swi = 132;

расширения типов:

Screen< shi, swi > bpObj2; // расширения типа short до int

template <unsigned int size> Buf{ ... };

преобразования целых типов:

Buf< 1024 > bpObj; // преобразование из int в unsigned int

(Более подробно они описаны в разделе 9.3.)

extern void foo( char * ); extern void bar( void * ); typedef void (*PFV)( void * ); const unsigned int x = 1024;

template <class Type, unsigned int size,

PFV handler> class Array { ... };

Array<int, 1024U, bar> a0;

// правильно: преобразование не нужно

Array<int, 1024U, foo> a1;

// ошибка: foo != PFV

Array<int, 1024, bar> a2;

// правильно: 1024 преобразуется в unsigned

int

// ошибка: foo != PFV

Array<int, 1024, bar> a3;

Array<int, x, bar> a4;

// правильно: преобразование не нужно

Рассмотрим следующие объявления:

Array<int, x, foo> a5; // ошибка: foo != PFV

Объекты a0 и a4 класса Array определены правильно, так как аргументы шаблона точно соответствуют типам параметров. Объект a2 также определен правильно, потому что аргумент 1024 типа int приводится к типу unsigned int параметра-константы size с помощью преобразования целых типов. Объявления a1, a3 и a5 ошибочны, так как не существует преобразования между любыми двумя типами функций.

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

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

793

template <int *ptr> class BufPtr { ... };

//ошибка: 0 имеет тип int

//неявное преобразование в нулевой указатель не применяется

BufPtr< 0 > nil;

Упражнение 16.3

Укажите, какие из данных конкретизированных шаблонов действительно приводят к

template < class

Type >

 

class Stack { };

 

void f1( Stack< char > );

// (a)

class Exercise {

 

 

// ...

 

// (b)

Stack< double > &rsd;

Stack< int >

si;

// (c)

};

 

 

int main() {

 

// (d)

Stack< char > *sc;

f1( *sc );

 

// (e)

int iObj = sizeof( Stack< string > ); // (f)

конкретизации:

}

Упражнение 16.4

template

<

int *ptr > class Ptr

(

... };

};

template

<

class Type, int size

>

class Fixed_Array { ...

Какие из следующих конкретизаций шаблонов корректны? Почему?

(a) const int size = 1024;

template < int hi, int wid > class Screen { ... };

(b)int arr[10]; Ptr< arr > bp2;

Ptr< &size > bp1;

(c)Ptr < 0 > bp3;

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

794

(d)const int hi = 40; const int wi = 80;

(e)const int size_val = 1024;

(f)unsigned int fasize = 255; Screen< hi, wi+32 > sObj;

Fixed_Array< string, size_val > fa1;

(g)const double db = 3.1415; Fixed_Array< int, fasize > fa2; Fixed_Array< double, db > fa3;

16.3. Функции-члены шаблонов классов

Как и для обычных классов, функция-член шаблона класса может быть определена либо внутри определения шаблона (и тогда называется встроенной), либо вне его. Мы уже встречались со встроенными функциями-членами при рассмотрении шаблона Queue. Например, конструктор Queue является встроенным, так как определен внутри

template <class Type> class Queue {

//...

public:

//встроенный конструктор

Queue() : front( 0 ), back( 0 ) { }

// ...

определения шаблона класса:

};

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

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

795

template <class Type> class Queue { public:

Queue();

private: // ...

};

template <class Type> inline Queue<Type>::

Queue( ) { front = back = 0; }

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

Функция-член шаблона класса сама является шаблоном. Стандарт C++ требует, чтобы она конкретизировалась только при вызове либо при взятии ее адреса. (Некоторые более

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

Queue<string> qs;

Объект qs имеет тип Queue<string>. При инициализации объекта этого класса вызывается конструктор Queue<string>. В данном случае аргументом, которым конкретизируется функция-член (конструктор), будет string.

Функция-член шаблона конкретизируется только при реальном использовании в программе (т.е. при вызове или взятии ее адреса). От того, в какой именно момент конкретизируется функция-член, зависит разрешение имен в ее определении (см. раздел 16.11) и объявление ее специализации (см. раздел 16.9).

16.3.1. Функции-члены шаблонов Queue и QueueItem

Чтобы понять, как определяются и используются функции-члены шаблонов классов, продолжим изучение шаблонов Queue и QueueItem:

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

796

template <class Type> class Queue { public:

Queue() : front( 0 ), back ( 0 ) { } ~Queue();

Type& remove();

void add( const Type & ); bool is_empty() const { return front == 0;

}

private:

QueueItem<Type> *front; QueueItem<Type> *back;

};

Деструктор, а также функции-члены remove() и add() определены не в теле шаблона, а

template <class Type> Queue<Type>::~Queue()

{

while (! is_empty() ) remove();

вне его. Деструктор Queue опустошает очередь:

}

template <class Type>

void Queue<Type>::add( const Type &val )

{

// создать новый объект QueueItem QueueItem<Type> *pt =

new QueueItem<Type>( val );

if ( is_empty() ) front = back = pt;

else

{

back->next = pt; back = pt;

}

Функция-член Queue<Type>::add() помещает новый элемент в конец очереди:

}

Функция-член Queue<Type>::remove() возвращает значение элемента, находящегося в начале очереди, и удаляет сам элемент.

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

797

 

 

#include <iostream>

 

 

 

 

 

 

#include <cstdlib>

 

 

 

template <class Type>

 

 

 

Type Queue<Type>::remove()

 

 

 

{

 

 

 

if ( is_empty() )

 

 

 

{

 

 

 

cerr << "remove() вызвана для пустой очереди\n";

 

 

 

exit( -1 );

 

 

 

}

 

 

 

QueueItem<Type> *pt = front;

 

 

 

front = front->next;

 

 

 

Type retval = pt->item;

 

 

 

delete pt;

 

 

 

return retval;

 

 

 

}

 

 

 

 

 

 

 

Мы поместили определения функций-членов в заголовочный файл Queue.h, включив его

 

в каждый файл, где возможны конкретизации функций. (Обоснование этого решения, а

 

также рассмотрение более общих вопросов, касающихся модели компиляции шаблонов,

 

мы отложим до раздела 16.8.)

 

В следующей программе иллюстрируется использование и конкретизация функции-члена

 

 

 

#include <iostream>

 

 

 

 

 

 

#include "Queue.h"

 

 

 

int main()

 

 

 

{

 

 

 

// конкретизируется класс Queue<int>

 

 

 

// оператор new требует, чтобы Queue<int> был определен

 

 

 

Queue<int> *p_qi = new Queue<int>;

 

 

 

int ival;

 

 

 

for ( ival = 0; ival < 10; ++ival )

 

 

 

// конкретизируется функция-член add()

 

 

 

p_qi->add( ival );

 

 

 

int err_cnt = 0;

 

 

 

for ( ival = 0; ival < 10; ++ival ) {

 

 

 

// конкретизируется функция-член remove()

 

 

 

int qval = p_qi->remove();

 

 

 

if ( ival != qval ) err_cnt++;

 

 

 

}

 

 

 

if ( !err_cnt )

 

 

 

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

 

 

 

else cerr << "?? queue errors: " << err_cnt << endl;

 

 

 

return 0;

 

шаблона Queue:

 

 

 

}

 

 

 

 

 

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

 

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

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> & );