<< Пред.           стр. 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 class BufPtr { ... };
 
 int array[10];
 BufPtr< array > bpObj; // преобразование массива в указатель
 преобразования квалификаторов:
 template class Ptr { ... };
 
 int iObj;
 Ptr< &iObj > pObj; // преобразование из int* в const int*
 расширения типов:
 template class Screen { ... };
 
 const short shi = 40;
 const short swi = 132;
 Screen< shi, swi > bpObj2; // расширения типа short до int
 преобразования целых типов:
 template 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   unsigned int size,
  PFV handler> class Array { ... };
 
 Array a0; // правильно: преобразование не нужно
 Array a1; // ошибка: foo != PFV
 
 Array a2; // правильно: 1024 преобразуется в unsigned int
 Array a3; // ошибка: foo != PFV
 
 Array a4; // правильно: преобразование не нужно
 Array a5; // ошибка: foo != PFV
 Объекты 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;
 Объект qs имеет тип Queue. При инициализации объекта этого класса вызывается конструктор Queue. В данном случае аргументом, которым конкретизируется функция-член (конструктор), будет string.
 Функция-член шаблона конкретизируется только при реальном использовании в программе (т.е. при вызове или взятии ее адреса). От того, в какой именно момент конкретизируется функция-член, зависит разрешение имен в ее определении (см. раздел 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 *front;
  QueueItem *back;
 };
 Деструктор, а также функции-члены remove() и add() определены не в теле шаблона, а вне его. Деструктор Queue опустошает очередь:
 template
 Queue::~Queue()
 {
  while (! is_empty() )
  remove();
 }
 Функция-член Queue::add() помещает новый элемент в конец очереди:
 template
 void Queue::add( const Type &val )
 {
  // создать новый объект QueueItem
  QueueItem *pt =
  new QueueItem( val );
 
  if ( is_empty() )
  front = back = pt;
  else
  {
  back->next = pt;
  back = pt;
  }
 }
 Функция-член Queue::remove() возвращает значение элемента, находящегося в начале очереди, и удаляет сам элемент.
 #include
 #include
 
 template
 Type Queue::remove()
 {
  if ( is_empty() )
  {
  cerr << "remove() вызвана для пустой очереди\n";
  exit( -1 );
  }
 
  QueueItem *pt = front;
  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 *p_qi = new 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( QueueItem );
  friend void Queue::bar();
 
  // ...
 };
 Прежде чем шаблон класса можно будет использовать в объявлениях друзей, он сам должен быть объявлен или определен. В нашем примере шаблоны классов foobar и Queue, а также шаблон функции foo() следует объявить до того, как они объявлены друзьями в QueueItem.
 Синтаксис, использованный для объявления foo() другом, может показаться странным:
 friend void foo( QueueItem );
 За именем функции следует список явных аргументов шаблона: foo. Такой синтаксис показывает, что в качестве друга объявляется конкретизированный шаблон функции foo(). Если бы список явных аргументов был опущен:
 friend void foo( QueueItem );
 то компилятор интерпретировал бы объявление как относящееся к обычной функции (а не к шаблону), у которой тип параметра – это экземпляр шаблона QueueItem. Как отмечалось в разделе 10.6, шаблон функции и одноименная обычная функция могут сосуществовать, и присутствие объявления такого шаблона перед определением класса QueueItem не вынуждает компилятор соотнести объявление друга именно с ним. Для того, чтобы соотнесение было верным, в конкретизированном шаблоне функции необходимо указать список явных аргументов;
 несвязанный дружественный шаблон класса или функции. В следующем примере имеется отображение один-ко-многим между конкретизациями шаблона класса QueueItem и его друзьями. Для каждой конкретизации типа QueueItem все конкретизации foobar, foo() и Queue::bar() являются друзьями:
 template
 class QueueItem {
  template
  friend class foobar;
 
  template
  friend void foo( QueueItem );
 
  template
  friend class Queue::bar();
 
  // ...
 };
 Следует отметить, что этот вид объявлений друзей в шаблоне класса не поддерживается компиляторами, написанными до принятия стандарта C++.
 16.4.1. Объявления друзей в шаблонах Queue и QueueItem
 Поскольку QueueItem не предназначен для непосредственного использования в вызывающей программе, то объявление конструктора этого класса помещено в закрытую секцию шаблона. Теперь класс Queue необходимо объявить другом QueueItem, чтобы можно было создавать и манипулировать объектами последнего.
 Существует два способа объявить шаблон класса другом. Первый заключается в том, чтобы объявить любой экземпляр Queue другом любого экземпляра QueueItem:
 template
 class QueueItem {
  // любой экземпляр Queue является другом
  // любого экземпляра QueueItem
  template friend class Queue;
 };
 Однако нет смысла объявлять, например, класс Queue, конкретизированный типом string, другом QueueItem, конкретизированного типом complex. Queue должен быть другом только для класса QueueItem. Таким образом, нам нужно взаимно однозначное соответствие между экземплярами Queue и QueueItem, конкретизированными одинаковыми типами. Чтобы добиться этого, применим второй метод объявления друзей:
 template
 class QueueItem {

<< Пред.           стр. 75 (из 121)           След. >>

Список литературы по разделу