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

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

789

В данном определении аргумент передается по значению. Это допустимо, если QueueItem конкретизируется встроенным типом (например, QueueItem<int>). Но если такая конкретизация производится для объемного типа (скажем, Matrix), то накладные расходы, вызванные неправильным выбором на этапе проектирования, становятся неприемлемыми. (В разделе 7.3 обсуждались вопросы производительности, связанные с передачей параметров по значению и по ссылке.) Поэтому аргумент конструктора объявляется как ссылка на константный тип:

QueueItem( const Type & );

Следующее определение приемлемо, если у типа, для которого конкретизируется

template <class Type> class QueueItem {

//...

public:

//потенциально неэффективно

QueueItem( const Type &t ) { item = t; next = 0;

}

QueueItem, нет ассоциированного конструктора:

};

Если аргументом шаблона является тип класса с конструктором (например, string), то item инициализируется дважды! Конструктор по умолчанию string вызывается для инициализации item перед выполнением тела конструктора QueueItem. Затем для созданного объекта item производится почленное присваивание. Избежать такого можно

спомощью явной инициализации item в списке инициализации членов внутри

template <class Type> class QueueItem {

//...

public:

//item инициализируется в списке инициализации членов конструктора

QueueItem( const Type &t )

:item(t) { next = 0; }

определения конструктора QueueItem:

};

(Списки инициализации членов и основания для их применения обсуждались в разделе

14.5.)

16.2.1. Аргументы шаблона для параметров-констант

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

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

790

template <int hi, int wid> class Screen {

public:

Screen() : _height( hi ), _width( wid ), _cursor ( 0 ), _screen( hi * wid, '#' )

{ }

// ...

 

private:

_screen;

string

string::size_type _cursor;

short

_height;

short

_width;

};

 

typedef Screen<24,80> termScreen; termScreen hp2621;

Screen<8,24> ancientScreen;

Выражение, с которым связан параметр, не являющийся типом, должно быть константным, т.е. вычисляемым во время компиляции. В примере выше typedef termScreen ссылается на экземпляр шаблона Screen<24,80>, где аргумент шаблона для hi равен 24, а для wid 80. В обоих случаях аргумент это константное выражение.

Однако для шаблона BufPtr конкретизация приводит к ошибке, так как значение указателя, получающееся при вызове оператора new(), становится известно только во

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

// ошибка: аргумент шаблона нельзя вычислить во время компиляции

время выполнения:

BufPtr< new int[24] > bp;

Не является константным выражением и значение неконстантного объекта. Его нельзя использовать в качестве аргумента для параметра-константы шаблона. Однако адрес любого объекта в области видимости пространства имен, в отличие от адреса локального объекта, является константным выражением (даже если спецификатор const отсутствует), поэтому его можно применять в качестве аргумента для параметра-

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

int size_val = 1024;

const int c_size_val = 1024;

Buf< 1024 > buf0; // правильно

Buf< c_size_val > buf1; // правильно

Buf< sizeof(size_val) > buf2; // правильно: sizeof(int)

BufPtr< &size_val > bp0; // правильно

// ошибка: нельзя вычислить во время компиляции

константы. Константным выражением будет и значение оператора sizeof:

Buf< size_val > buf3;

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

791

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

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 ссылаются на один и тот

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<24,80>:

Screen< width, height > scr2;

Между типом аргумента шаблона и типом параметра-константы допустимы некоторые преобразования. Их множество является подмножеством преобразований, допустимых для аргументов функции:

трансформации l-значений, включающие преобразование l-значения в r-

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

int array[10];

значение, массива в указатель и функции в указатель:

BufPtr< array > bpObj; // преобразование массива в указатель

template <const int *ptr> class Ptr { ... }; int iObj;

преобразования квалификаторов:

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

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;