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

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

 
 int ai[4] = {12, 8, 73, 45 };
 
 int main() {
  int size = sizeof(ai) / sizeof(int);
  // конкретизируется min( int*, int )
  min( &ai[0], size );
 }
 main() вызывает конкретизированную из шаблона функцию min(int*,int). В этой реализации Type заменено int, и тип переменной min_val, следовательно, равен int. Поэтому при обращении print(min_val) вызывается функция с аргументом типа int. Именно тогда, когда конкретизируется min(int*,int), становится известно, что при втором вызове аргумент print() имеет тип int. В этот момент такая функция должна быть видима. Если бы функция print(int) не была объявлена до конкретизации min(int*,int), то компилятор выдал бы сообщение об ошибке.
 Поэтому разрешение имен в определении шаблона происходит в два этапа. Сначала разрешаются имена, не зависящие от его параметров, а затем, при конкретизации, – имена, зависящие от параметров.
 Но зачем нужны два шага? Почему бы, например, не разрешать все имена при конкретизации?
 Если вы проектируете шаблон функции, то, вероятно, хотели бы сохранить контроль над тем, когда разрешаются имена в его определении. Предположим, что шаблон min() – это часть библиотеки, в которой определены и другие шаблоны и функции. Желательно, чтобы реализации min() по возможности использовали другие компоненты нашей же библиотеки. В предыдущем примере интерфейс библиотеки определен в заголовочном файле

. Как объявление функции print(const char*), так и определение функции min() являются частями интерфейса. Мы хотим, чтобы конкретизации шаблона min() пользовались функцией print() из нашей библиотеки. Первый этап разрешения имени это гарантирует. Если имя, использованное в определении шаблона, не зависит от его параметров, то оно обязательно будет относиться к компоненту внутри библиотеки, т.е. к тому объявлению, которое включено в один пакет с этим определением в заголовочном файле

.
 На самом деле автор шаблона должен позаботиться о том, чтобы были объявлены все имена, использованные в определениях и не зависящие от параметров. Если этого нет, то определение шаблона вызовет ошибку. При конкретизации шаблона компилятор ее не исправляет:
 // ---- primer.h ----
 template
  Type min( Type* array, int size )
 {
  Type min_val = array[0];
  // ...
  // ошибка: функция print( const char* ) не найдена
  print( "Minimum value found: " );
 
  // правильно: зависит от параметра шаблона
  print( min_val );
  // ...
 }
 
 // ---- user.C ----
 #include


 
 // это объявление print( const char* ) игнорируется
 void print( const char* );
 void print( int );
 
 int ai[4] = {12, 8, 73, 45 };
 
 int main() {
  int size = sizeof(ai) / sizeof(int);
  // конкретизируется min( int*, int )
  min( &ai[0], size );
 }
 Объявление функции print( const char* ) в файле user.C невидимо в том месте, где появляется определение шаблона. Однако оно видимо там, где конкретизируется шаблон min(int*,int), но это объявление не рассматривается при компиляции вызова print("Minimum value found: "), так как последний не зависит от параметров шаблона. Если некоторая конструкция в определении шаблона не зависит от его параметров, то имена разрешаются в контексте самого определения, и результат разрешения в дальнейшем не пересматривается. Поэтому на программиста возлагается ответственность за то, чтобы объявления имен, встречающихся в определении, были включены в интерфейс библиотеки вместе с шаблоном.
 А теперь предположим, что библиотека была написана кем-то другим, а мы ее пользователи, которым доступен интерфейс, определенный в заголовочном файле

. Иногда нужно, чтобы объекты и функции, определенные в нашей программе, учитывались при конкретизации шаблона из библиотеки. Допустим, мы определили в своей программе класс SmallInt и хотели бы конкретизировать функцию min() из библиотеки

для получения минимального значения в массиве объектов типа SmallInt.
 При конкретизации шаблона min() для массива объектов типа SmallInt вместо аргумента шаблона Type подставляется тип SmallInt. Следовательно, min_val в конкретизированной функции min() имеет тот же тип. Тогда как разрешится вызов функции print(min_val)?
 // ---- user.h ----
 class SmallInt { /* ... */ }
 void print( const SmallInt & );
 
 // ---- user.C ----
 #include


 #include "user.h"
 SmallInt asi[4];
 
 int main() {
  // задать значения элементов массива asi
 
  // конкретизируется min( SmallInt*, int )
  // int size = sizeof(asi) / sizeof(SmallInt);
  min( &asi[0], size );
 }
 Это нормально: мы хотим, чтобы учитывалась именно наша функция print(const SmallInt &). Рассмотрения функций, определенных в библиотеке

, недостаточно. Второй шаг разрешения имени гарантирует, что если имя, использованное в определении, зависит от параметров шаблона, то принимаются во внимание имена, объявленные в контексте конкретизации. Поэтому можно быть уверенным, что функции, умеющие манипулировать объектами типа SmallInt, попадут в поле зрения компилятора при анализе шаблона, которому в качестве аргумента передан тип SmallInt.
 Место в программе, где происходит конкретизация шаблона, называется точкой конкретизации. Знание этой точки важно потому, что она определяет, какие объявления учитывает компилятор для имен, зависящих от параметров шаблона. Такая точка всегда находится в области видимости пространства имен и следует за функцией, внутри которой произошла конкретизация. Например, точка конкретизации min(SmallInt*,int) расположена сразу после функции main() в области видимости пространства имен:
 // ...
 int main() {
  // ...
  // использование min(SmallInt*,int)
  min( &asi[0], size );
 }
 // точка конкретизации min(SmallInt*,int)
 // как будто объявление конкретизированной функции выглядит так:
 SmallInt min( SmallInt* array, int size )
  { /* ... */ }
 Но что, если конкретизация шаблона случается в одном исходном файле несколько раз? Где тогда будет точка конкретизации? Вы можете спросить: “А какая, собственно, разница?” В нашем примере для SmallInt разница есть, поскольку объявление функции print(const SmallInt &) должно появиться перед точкой конкретизации min(SmallInt*,int):
 #include


 void another();
 
 SmallInt asi[4];
 
 int main() {
  // задать значения элементов массива asi
  int size = sizeof(asi) / sizeof(SmallInt);
  min( &asi[0], size );
 
  another();
  // ...
 }
 // точка конкретизации здесь?
 
 void another() {
  int size = sizeof(asi) / sizeof(SmallInt);
  min( &asi[0], size );
 }
 // или здесь?
 В действительности точка конкретизации находится после определения каждой функции, в которой используется конкретизированный экземпляр. Компилятор может выбрать любую из этих точек, чтобы конкретизировать в ней шаблон. Отсюда следует, что при организации кода программы надо быть внимательным и помещать все объявления, необходимые для разрешения имен, зависящих от параметров некоторого шаблона, перед первой точкой. Поэтому разумно поместить их в заголовочный файл, который включается перед любой возможной конкретизацией шаблона:
 #include


 // user.h содержит объявления, необходимые при конкретизации
 #include "user.h"
 void another();
 
 SmallInt asi[4];
 
 int main() {
  // ...
 }
 // первая точка конкретизации min(SmallInt*,int)
 
 void another() {
  // ...
 }
 // вторая точка конкретизации min(SmallInt*,int)
 А если конкретизация шаблона происходит в нескольких файлах? Например, что будет, если функция another() находится в другом файле, нежели main()? Тогда точка конкретизации есть в каждом файле, где используется конкретизированная из шаблона функция. Компилятор свободен в выборе любой из них, так что нам снова придется проявить аккуратность и включить файл "user.h" во все исходные файлы, где используются конкретизированные функции. Тем самым гарантируется, что реализация min(SmallInt*,int) будет ссылаться именно на нашу функцию print(const SmallInt &) вне зависимости от того, какую из точек конкретизации выберет компилятор.
 Упражнение 10.13
 Назовите два шага разрешения имени в определениях шаблона. Объясните, каким образом первый шаг отвечает потребностям разработчика библиотеки, а второй обеспечивает гибкость, необходимую пользователям шаблонов.
 Упражнение 10.14
 На какие объявления ссылаются имена display и SIZE в реализации max(LongDouble*,SIZE)?
 // ---- exercise.h ----
 void display( const void* );
 typedef unsigned int SIZE;
 
 template
  Type max( Type* array, SIZE size )
 {
  Type max_val = array[0];
  for ( SIZE i = 1; i < size; ++i )
  if ( array[i] > max_val )
  max_val = array[i];
 
  display( "Maximum value found: " );
  display( max_val );
 
  return max_val;
 }
 // ---- user.h ----
 class LongDouble { /* ... */ };
 void display( const LongDouble & );
 void display( const char * );
 typedef int SIZE;
 
 // ---- user.C ----
 #include
 #include "user.h"
 
 LongDouble ad[7];
 
 int main() {
  // задать значения элементов массива ad
 
  // конкретизируется max( LongDouble*, SIZE )
  SIZE size = sizeof(ad) / sizeof(LongDouble);
 
  max( &ad[0], size );
 }
 10.10. Пространства имен и шаблоны функций А
 Как и любое другое глобальное определение, шаблон функции может быть помещен в пространство имен (см. обсуждение пространств имен в разделах 8.5 и 8.6). Мы получили бы ту же семантику, если бы определили шаблон в глобальной области видимости, скрыв его имя внутри пространства имен. При использовании вне этого пространства необходимо либо квалифицировать имя шаблона именем пространства имен, либо использовать using-объявление:
 // ---- primer.h ----
 namespace cplusplus_primer {
  // определение шаблона скрыто в пространстве имен
  template
  Type min( Type* array, int size ) { /* ... */ }
 }
 
 // ---- user.C ----
 #include


 int ai[4] = { 12, 8, 73, 45 };
 
 int main() {
  int size = sizeof(ai) / sizeof(ai[0]);
 
  // ошибка: функция min() не найдена
  min( &ai[0], size );
 
  using cplusplus_primer::min; // using-объявление
  // правильно: относится к min() в пространстве имен cplusplus_primer
  min( &ai[0], size );
 }
 Что произойдет, если наша программа использует шаблон, определенный в пространстве имен, и мы хотим предоставить для него специализацию? (Явные специализации шаблонов рассматривались в разделе 10.6.) Допустим, мы хотим использовать шаблон min(), определенный в cplusplus_primer, для нахождения минимального значения в массиве объектов типа SmallInt. Однако мы осознаем, что имеющееся определение шаблона не вполне подходит, поскольку сравнение в нем выглядит так:
 if ( array[i] < min_val )
 В этой инструкции два объекта класса SmallInt сравниваются с помощью оператора <. Но этот оператор неприменим к объектам, если только не перегружен в классе SmallInt (мы покажем, как определять перегруженные операторы в главе 15). Предположим, что мы хотели бы определить специализацию шаблона min(), чтобы она пользовалась функцией compareLess() для сравнения двух подобных объектов. Вот ее объявление:
 // функция сравнения объектов SmallInt
 // возвращает true, если parm1 меньше parm2
 bool compareLess( const SmallInt &parm1, const SmallInt &parm2 );
 Как должно выглядеть определение этой функции? Чтобы ответить на этот вопрос, необходимо познакомиться с определением класса SmallInt более подробно. Данный класс позволяет определять объекты, которые хранят тот же диапазон значений, что и 8-разрядный тип unsigned char, т.е. от 0 до 255. Дополнительная функциональность состоит в том, что класс перехватывает ошибки переполнения и потери значимости. Во всем остальном он должен вести себя точно так же, как unsigned char. Определение SmallInt выглядит следующим образом:
 class SmallInt {
 public:
  SmallInt( int ival ) : value( ival ) {}
  friend bool compareLess( const SmallInt &, const SmallInt & );
 private:
  int value; // член
 };
 В этом классе есть один закрытый член value, в котором хранится значение объекта типа SmallInt. Класс также содержит конструктор с параметром ival:
 // конструктор класса SmallInt
 SmallInt( int ival ) : value( ival ) {}
 Его единственное назначение – инициализировать член класса value значением ival.
 Вот теперь можно ответить на ранее поставленный вопрос: как должна быть определена функция compareLess()? Она будет сравнивать члены value переданных ей аргументов типа SmallInt:
 // возвращает true, если parm1 меньше parm2
 bool compareLess( const SmallInt &parm1, const SmallInt &parm2 ) {
  return parm1.value < parm2.value;
 }
 Заметим, однако, что член value является закрытым. Как может глобальная функция обратиться к закрытому члену, не нарушив инкапсуляции класса SmallInt и не вызвав тем самым ошибку компиляции? Если вы посмотрите на определение класса SmallInt, то заметите, что глобальная функция compareLess() объявлена как дружественная (friend). Если функция объявлена таким образом, то ей доступны закрытые члены класса. (Друзья классов рассматриваются в разделе 15.2.)
 Теперь мы готовы определить специализацию шаблона min(). Она следующим образом использует функцию compareLess().
 // специализация min() для массива объектов SmallInt
 template<> SmallInt min( SmallInt* array, int size )
 {
  SmallInt min_val = array[0];
  for (int i = 1; i < size; ++i)
  // при сравнении используется функция compareLess()
  if ( compareLess( array[i], min_val ) )
  min_val = array[i];
 
  print( "Minimum value found: " );
  print( min_val );
 
  return min_val;
 }
 Где мы должны объявить эту специализацию? Предположим, что здесь:
 // ---- primer.h ----
 namespace cplusplus_primer {
  // определение шаблона скрыто в пространстве имен
  template
  Type min( Type* array, int size ) { /* ... */ }
 }
 
 // ---- user.h ----
 class SmallInt { /* ... */ };
 void print( const SmallInt & );
 bool compareLess( const SmallInt &, const SmallInt & );
 
 // ---- user.C ----
 #include


 #include "user.h"
 
 // ошибка: это не специализация для cplusplus_primer::min()
 template<> SmallInt min( SmallInt* array, int size )
  { /* ... */ }
 // ...
 К сожалению, этот код не работает. Явная специализация шаблона функции должна быть объявлена в том пространстве имен, где определен порождающий шаблон. Поэтому мы обязаны определить специализацию min() в пространстве cplusplus_primer. В нашей программе это можно сделать двумя способами.
 Напомним, что определения пространства имен не обязательно непрерывны. Мы можем повторно открыть пространство имен cplusplus_primer для добавления специализации:
 // ---- user.C ----
 #include


 #include "user.h"
 
 namespace cplusplus_primer {
  // специализация для cplusplus_primer::min()
  template<> SmallInt min( SmallInt* array, int size )
  { /* ... */ }
 }
 SmallInt asi[4];
 
 int main() {
  // задать значения элементов массива asi с помощью функции-члена set()
 
  using cplusplus_primer::min; // using-объявление
  int size = sizeof(asi) / sizeof(SmallInt);
  // конкретизируется min(SmallInt*,int)
  min( &asi[0], size );
 }
 Можно определить специализацию так, как мы определяем любой другой член пространства имен вне определения самого пространства: квалифицировав имя члена именем объемлющего пространства.
 // ---- user.C ----
 #include


 #include "user.h"
 
 // специализация для cplusplus_primer::min()
 // имя специализации квалифицируется
 namespace {
 template<> SmallInt cplusplus_primer::
  min( SmallInt* array, int size )
  { /* ... */ }
 // ...
 Если вы, пользуясь библиотекой, содержащей определения шаблонов, захотите написать их специализации, то должны будете удостовериться, что их определения помещены в то же пространство имен, что и определения исходных шаблонов.
 Упражнение 10.15
 Поместим содержимое заголовочного файла из упражнения 10.14 в пространство имен cplusplus_primer. Как надо изменить функцию main(), чтобы она могла конкретизировать шаблон max(), находящийся в cplusplus_primer?
 Упражнение 10.16
 Снова обращаясь к упражнению 10.14, предположим, что содержимое заголовочного файла помещено в пространство имен cplusplus_primer. Допустим, мы хотим специализировать шаблон функции max() для массивов объектов класса LongDouble. Нужно, чтобы специализация шаблона использовала функцию compareGreater() для сравнения двух объектов класса LongDouble, объявленную как:
 // функция сравнения объектов класса LongDouble
 // возвращает true, если parm1 больше parm2
 bool compareGreater( const LongDouble &parm1,
  const LongDouble &parm2 );
 Определение класса LongDouble выглядит следующим образом:
 class LongDouble {
 public:
  LongDouble(double dval) : value(ival) {}
  friend bool compareGreater( const LongDouble &,
  const LongDouble & );
 private:
  double value;
 };
 Напишите определение функции compareGreater() и специализацию max(), в которой эта функция используется. Напишите также функцию main(), которая задает элементы массива ad, а затем вызывает специализацию max(), доставляющую его максимальный элемент. Значения, которыми инициализируется массив ad, должны быть получены чтением из стандартного ввода cin.
 10.11. Пример шаблона функции
 В этом разделе приводится пример, показывающий, как можно определять и использовать шаблоны функций. Здесь определяется шаблон sort(), который затем применяется для сортировки элементов массива. Сам массив представлен шаблоном класса Array (см. раздел 2.5). Таким образом, шаблоном sort() можно пользоваться для сортировки массивов элементов любого типа.
 В главе 6 мы видели, что в стандартной библиотеке C++ определен контейнерный тип vector, который ведет себя во многом аналогично типу Array. В главе 12 рассматриваются обобщенные алгоритмы, способные манипулировать контейнерами, описанными в главе 6. Один из таких алгоритмов, sort(), служит для сортировки содержимого вектора. В этом разделе мы определим собственный “обобщенный алгоритм sort()” для манипулирования классом Array, упрощенной версии алгоритма из стандартной библиотеки C++.
 Шаблон функции sort() для шаблона класса Array определен следующим образом:
 template
  void sort( Array &array, int low, int high ) {
 
  if ( low < high ) {
  int lo = low;
  int hi = high + 1;
  elemType elem = array[lo];
 
  for (;;) {

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

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