<< Пред. стр. 75 (из 121) След. >>
Вот еще один пример, иллюстрирующий использование параметра-константы для представления константного значения в определении шаблона, а также применение его аргумента для задания значения этого параметра:template < class Type, int size >
class FixedArray {
public:
FixedArray( Type *ar ) : count( size )
{
for ( int ix = 0; ix < size; ++ix )
array[ ix ] = ar[ ix ];
}
private:
Type array[ size ];
int count;
};
int ia[4] = { 0, 1, 2, 3 };
FixedArray< int, sizeof( is ) / sizeof( int ) > iA{ ia );
Выражения с одинаковыми значениями считаются эквивалентными аргументами для параметров-констант шаблона. Так, все три экземпляра Screen ссылаются на один и тот же конкретизированный из шаблона класс Screen<24,80>:
const int width = 24;
const int height = 80;
// все это Screen< 24, 80 >
Screen< 2*12, 40*2 > scr0;
Screen< 6+6+6+6, 20*2 + 40 > scr1;
Screen< width, height > scr2;
Между типом аргумента шаблона и типом параметра-константы допустимы некоторые преобразования. Их множество является подмножеством преобразований, допустимых для аргументов функции:
трансформации l-значений, включающие преобразование l-значения в r-значение, массива в указатель и функции в указатель:
template
int array[10];
BufPtr< array > bpObj; // преобразование массива в указатель
преобразования квалификаторов:
template
int iObj;
Ptr< &iObj > pObj; // преобразование из int* в const int*
расширения типов:
template
const short shi = 40;
const short swi = 132;
Screen< shi, swi > bpObj2; // расширения типа short до int
преобразования целых типов:
template
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
PFV handler> class Array { ... };
Array
Array
Array
Array
Array
Array
Объекты a0 и a4 класса Array определены правильно, так как аргументы шаблона точно соответствуют типам параметров. Объект a2 также определен правильно, потому что аргумент 1024 типа int приводится к типу unsigned int параметра-константы size с помощью преобразования целых типов. Объявления a1, a3 и a5 ошибочны, так как не существует преобразования между любыми двумя типами функций.
Приведение значения 0 целого типа к типу указателя недопустимо:
template
class BufPtr { ... };
// ошибка: 0 имеет тип int
// неявное преобразование в нулевой указатель не применяется
BufPtr< 0 > nil;
Упражнение 16.3
Укажите, какие из данных конкретизированных шаблонов действительно приводят к конкретизации:
template < class Type >
class Stack { };
void f1( Stack< char > ); // (a)
class Exercise {
// ...
Stack< double > &rsd; // (b)
Stack< int > si; // (c)
};
int main() {
Stack< char > *sc; // (d)
f1( *sc ); // (e)
int iObj = sizeof( Stack< string > ); // (f)
}
Упражнение 16.4
Какие из следующих конкретизаций шаблонов корректны? Почему?
template < int *ptr > class Ptr ( ... };
template < class Type, int size > class Fixed_Array { ... };
template < int hi, int wid > class Screen { ... };
(a) const int size = 1024;
Ptr< &size > bp1;
(b) int arr[10];
Ptr< arr > bp2;
(c) Ptr < 0 > bp3;
(d) const int hi = 40;
const int wi = 80;
Screen< hi, wi+32 > sObj;
(e) const int size_val = 1024;
Fixed_Array< string, size_val > fa1;
(f) unsigned int fasize = 255;
Fixed_Array< int, fasize > fa2;
(g) const double db = 3.1415;
Fixed_Array< double, db > fa3;
16.3. Функции-члены шаблонов классов
Как и для обычных классов, функция-член шаблона класса может быть определена либо внутри определения шаблона (и тогда называется встроенной), либо вне его. Мы уже встречались со встроенными функциями-членами при рассмотрении шаблона Queue. Например, конструктор Queue является встроенным, так как определен внутри определения шаблона класса:
template
class Queue {
// ...
public:
// встроенный конструктор
Queue() : front( 0 ), back( 0 ) { }
// ...
};
При определении функции-члена шаблона вне определения самого шаблона следует применять специальный синтаксис для обозначения того, членом какого именно шаблона является функция. Определению функции-члена должно предшествовать ключевое слово template, за которым следуют параметры шаблона. Так, конструктор Queue можно определить следующим образом:
template
class Queue {
public:
Queue();
private:
// ...
};
template
inline Queue
Queue( ) { front = back = 0; }
За первым вхождением Queue (перед оператором ::) следует список параметров, показывающий, какому шаблону принадлежит данная функция-член. Второе вхождение Queue в определение конструктора (после оператора ::) содержит имя функции-члена, за которым может следовать список параметров шаблона, хотя это и необязательно. После имени функции идет ее определение;. в нем могут быть ссылки на параметр шаблона Type всюду, где в определении обычной функции использовалось бы имя типа.
Функция-член шаблона класса сама является шаблоном. Стандарт C++ требует, чтобы она конкретизировалась только при вызове либо при взятии ее адреса. (Некоторые более старые компиляторы конкретизируют такие функции одновременно с конкретизацией самого шаблона класса.) При конкретизации функции-члена используется тип того объекта, для которого функция вызвана:
Queue
Объект qs имеет тип Queue
Функция-член шаблона конкретизируется только при реальном использовании в программе (т.е. при вызове или взятии ее адреса). От того, в какой именно момент конкретизируется функция-член, зависит разрешение имен в ее определении (см. раздел 16.11) и объявление ее специализации (см. раздел 16.9).
16.3.1. Функции-члены шаблонов Queue и QueueItem
Чтобы понять, как определяются и используются функции-члены шаблонов классов, продолжим изучение шаблонов Queue и QueueItem:
template
class Queue {
public:
Queue() : front( 0 ), back ( 0 ) { }
~Queue();
Type& remove();
void add( const Type & );
bool is_empty() const {
return front == 0;
}
private:
QueueItem
QueueItem
};
Деструктор, а также функции-члены remove() и add() определены не в теле шаблона, а вне его. Деструктор Queue опустошает очередь:
template
Queue
{
while (! is_empty() )
remove();
}
Функция-член Queue
template
void Queue
{
// создать новый объект QueueItem
QueueItem
new QueueItem
if ( is_empty() )
front = back = pt;
else
{
back->next = pt;
back = pt;
}
}
Функция-член Queue
#include
#include
template
Type Queue
{
if ( is_empty() )
{
cerr << "remove() вызвана для пустой очереди\n";
exit( -1 );
}
QueueItem
front = front->next;
Type retval = pt->item;
delete pt;
return retval;
}
Мы поместили определения функций-членов в заголовочный файл Queue.h, включив его в каждый файл, где возможны конкретизации функций. (Обоснование этого решения, а также рассмотрение более общих вопросов, касающихся модели компиляции шаблонов, мы отложим до раздела 16.8.)
В следующей программе иллюстрируется использование и конкретизация функции-члена шаблона Queue:
#include
#include "Queue.h"
int main()
{
// конкретизируется класс Queue
// оператор new требует, чтобы Queue
Queue
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 executed ok
Упражнение 16.5
Используя шаблон класса Screen, определенный в разделе 16.2, реализуйте функции-члены Screen (см. разделы 13.3, 13.4 и 13.6) в виде функций-членов шаблона.
16.4. Объявления друзей в шаблонах классов
обычный (не шаблонный) дружественный класс или дружественная функция. В следующем примере функция foo(), функция-член bar() и класс foobar объявлены друзьями всех конкретизаций шаблона QueueItem:
class Foo {
void bar();
};
template
class QueueItem {
friend class foobar;
friend void foo();
friend void Foo::bar();
// ...
};
Ни класс foobar, ни функцию foo() не обязательно объявлять или определять в глобальной области видимости перед объявлением их друзьями шаблона QueueItem.
Однако перед тем как объявить другом какой-либо из членов класса Foo, необходимо определить его. Напомним, что член класса может быть введен в область видимости только через определение объемлющего класса. QueueItem не может ссылаться на Foo::bar(), пока не будет найдено определение Foo;
связанный дружественный шаблон класса или функции. В следующем примере определено взаимно однозначное соответствие между классами, конкретизированными по шаблону QueueItem, и их друзьями – также конкретизациями шаблонов. Для каждого класса, конкретизированного по шаблону QueueItem, ассоциированные конкретизации foobar, foo() и Queue::bar() являются друзьями.
template
class foobar { ... };
template
void foo( QueueItem
template
class Queue {
void bar();
// ...
};
template
class QueueItem {
friend class foobar
friend void foo
friend void Queue
// ...
};
Прежде чем шаблон класса можно будет использовать в объявлениях друзей, он сам должен быть объявлен или определен. В нашем примере шаблоны классов foobar и Queue, а также шаблон функции foo() следует объявить до того, как они объявлены друзьями в QueueItem.
Синтаксис, использованный для объявления foo() другом, может показаться странным:
friend void foo
За именем функции следует список явных аргументов шаблона: foo
friend void foo( QueueItem
то компилятор интерпретировал бы объявление как относящееся к обычной функции (а не к шаблону), у которой тип параметра – это экземпляр шаблона QueueItem. Как отмечалось в разделе 10.6, шаблон функции и одноименная обычная функция могут сосуществовать, и присутствие объявления такого шаблона перед определением класса QueueItem не вынуждает компилятор соотнести объявление друга именно с ним. Для того, чтобы соотнесение было верным, в конкретизированном шаблоне функции необходимо указать список явных аргументов;
несвязанный дружественный шаблон класса или функции. В следующем примере имеется отображение один-ко-многим между конкретизациями шаблона класса QueueItem и его друзьями. Для каждой конкретизации типа QueueItem все конкретизации foobar, foo() и Queue
template
class QueueItem {
template
friend class foobar;
template
friend void foo( QueueItem
template
friend class Queue
// ...
};
Следует отметить, что этот вид объявлений друзей в шаблоне класса не поддерживается компиляторами, написанными до принятия стандарта C++.
16.4.1. Объявления друзей в шаблонах Queue и QueueItem
Поскольку QueueItem не предназначен для непосредственного использования в вызывающей программе, то объявление конструктора этого класса помещено в закрытую секцию шаблона. Теперь класс Queue необходимо объявить другом QueueItem, чтобы можно было создавать и манипулировать объектами последнего.
Существует два способа объявить шаблон класса другом. Первый заключается в том, чтобы объявить любой экземпляр Queue другом любого экземпляра QueueItem:
template
class QueueItem {
// любой экземпляр Queue является другом
// любого экземпляра QueueItem
template
};
Однако нет смысла объявлять, например, класс Queue, конкретизированный типом string, другом QueueItem, конкретизированного типом complex
template
class QueueItem {