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

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

 void manipulate( int (*pf)( int,char ) );
 void manipulate( double (*pf)( float,float ) );
 
 int main()
 {
  // ошибка: какой из возможных экземпляров sum:
  // int sum( int,char ) или double sum( float, float )?
  manipulate( &sum );
 
  // берется адрес конкретизированного экземпляра
  // double sum( float, float )
  // вызывается: void manipulate( double (*pf)( float, float ) );
  manipulate( &sum< double, float, float > );
 }
 Отметим, что явное задание аргументов шаблона следует использовать только тогда, когда это абсолютно необходимо для разрешения неоднозначности или для конкретизации шаблона функции в контексте, где вывести аргументы невозможно. Во-первых, определение типов и значений аргументов шаблона проще оставить компилятору. А во-вторых, если мы модифицируем объявления в программе, так что типы аргументов функции при вызове конкретизированного шаблона изменятся, то компилятор автоматически скорректирует вызов без нашего вмешательства. С другой стороны, если аргументы шаблона заданы явно, необходимо проверить, что они по-прежнему отвечают новым типам аргументов функции. Поэтому мы рекомендуем избегать явного задания аргументов шаблона.
 Упражнение 10.6
 Назовите две ситуации, когда использование явного задания аргументов шаблона необходимо.
 Упражнение 10.7
 Пусть дано следующее определение шаблона функции sum():
 template
  T1 sum( T2, T3 );
 Какие из приведенных ниже вызовов ошибочны? Почему?
 double dobj1, dobj2;
 float fobj1, fobj2;
 char cobj1, cobj2;
 
 (a) sum( dobj1, dobj2 );
 (b) sum( fobj1, fobj2 );
 (c) sum( cobj1, cobj2 );
 (d) sum( fobj2, dobj2 );
 10.5. Модели компиляции шаблонов А
 Шаблон функции задает алгоритм для построения определений множества экземпляров функций. Сам шаблон не определяет никакой функции. Например, когда компилятор видит шаблон:
 template
  Type min( Type t1, Type t2 )
 {
  return t1 < t2 ? t1 : t2;
 }
 он сохраняет внутреннее представление min(), но и только. Позже, когда встретится ее реальное использование, скажем:
 int i, j;
 double dobj = min( i, j );
 компилятор строит определение min() по сохраненному внутреннему представлению.
 Здесь возникает несколько вопросов. Чтобы компилятор мог конкретизировать шаблон функции, должно ли его определение быть видимо при вызове экземпляра этой функции? Например, нужно ли определению шаблона min() появиться до ее конкретизации c целыми параметрами при инициализации dobj? Следует ли помещать шаблоны в заголовочные файлы, как мы поступаем с определениями встроенных (inline) функций? Или в заголовочные файлы можно помещать только объявления шаблонов, оставляя определения в файлах исходных текстов?
 Чтобы ответить на эти вопросы, нам придется объяснить принятую в C++ модель компиляции шаблонов, сформулировать требования к организации определений и объявлений шаблонов в программах. В C++ поддерживаются две таких модели: модель с включением и модель с разделением. В данном разделе описываются обе модели и объясняется их использование.
 10.5.1. Модель компиляции с включением
 Согласно этой модели мы включаем определение шаблона в каждый файл, где этот шаблон конкретизируется. Обычно оно помещается в заголовочный файл, как и для встроенных функций. Именно такой моделью мы пользуемся в нашей книге. Например:
 // model1.h
 // модель с включением:
 // определения шаблонов помещаются в заголовочный файл
 
 template
  Type min( Type t1, Type t2 ) {
 
  return t1 < t2 ? t1 : t2;
 }
 Этот заголовочный файл включается в каждый файл, где конкретизируется функция min():
 // определения шаблонов включены раньше
 // используется конкретизация шаблона
 #include "model1.h"
 
 int i, j;
 double dobj = min( i, j );
 Заголовочный файл можно включить в несколько файлов с исходными текстами программы. Означает ли это, что компилятор конкретизирует экземпляр функции min() с целыми параметрами в каждом файле, где имеется обращение к ней? Нет. Программа должна вести себя так, словно min() с целыми параметрами определена только один раз. Где и когда в действительности конкретизируется шаблон функции, оставляется на усмотрение разработчика компилятора. Нам достаточно знать, что где-то в программе нужная функция min() была конкретизирована. (Как мы покажем далее, с помощью явного объявления конкретизации можно указать, где и когда оно должно быть выполнено. Такие объявления желательно использовать на поздних стадиях разработки продукта для улучшения производительности.)
 Решение включать определения шаблонов функций в заголовочные файлы не всегда удачно. Тело шаблона описывает детали реализации, которые пользователям не интересны или которые мы хотели бы от них скрыть. В действительности, если определение шаблона велико, то количество кода в заголовочном файле может превысить разумные пределы. Кроме того, многократная компиляция одного и того же определения при обработке разных файлов увеличивает общее время компиляции программы. Отделить объявления шаблонов функций от их определений позволяет модель компиляции с разделением. Посмотрим, как ее можно использовать.
 10.5.2. Модель компиляции с разделением
 Согласно этой модели объявления шаблонов функций помещаются в заголовочный файл, а определения – в файл с исходным текстом программы, т.е. объявления и определения шаблонов организованы так же, как в случае с невстроенными (non-inline) функциями. Например:
 // model2.h
 // модель с разделением
 // сюда помещается только объявление шаблона
 
 template Type min( Type t1, Type t2 );
 
 // model2.C
 // определение шаблона
 export template
  Type min( Type t1, Type t2 ) { /* ... */ }
 Программа, которая конкретизирует шаблон функции min(), должна предварительно включить этот заголовочный файл:
 // user.C
 #include "model2.h"
 
 int i, j;
 double d = min ( i, j ); // правильно: здесь производится конкретизация
 Хотя определение шаблона функции min() не видно в файле user.c, конкретизацию min(int,int) произвести можно. Но для этого шаблон min() должен быть определен специальным образом. Вы уже заметили, как именно? Если вы внимательно посмотрите на файл model2.c, то увидите, что определению шаблона функции min() предшествует ключевое слово export. Таким образом, шаблон min() становится экспортируемым. Слово export говорит компилятору, что данное определение шаблона может понадобиться для конкретизации функций в других файлах. В таком случае компилятор должен гарантировать, что это определение будет доступно во время конкретизации.
 Для объявления экспортируемого шаблона перед ключевым словом template в его определении надо поместить слово export. Если шаблон экспортируется, то его разрешается конкретизировать в любом исходном файле программы – для этого нужно лишь объявить его перед использованием. Если слово export перед определением опущено, то компилятор может и не конкретизировать экземпляр функции min() с целыми параметрами и нам не удастся связать программу.
 Обратите внимание, что в некоторых реализациях это ключевое слово не нужно, поскольку поддерживается расширение языка, согласно которому неэкспортированный шаблон функции может встречаться только в одном исходном файле, при этом экземпляры такого шаблона в других файлах конкретизируются правильно. Однако подобное поведение не соответствует стандарту, который требует, чтобы пользователь всегда помечал определения шаблонов функций как экспортируемые, если объявление шаблона видно в исходном файле до его конкретизации.
 Ключевое слово export в объявлении шаблона, находящемся в заголовочном файле, можно опустить. Так, в объявлении min() в файле model2.h этого слова нет.
 Шаблон функции должен быть определен как экспортируемый только один раз во всей программе. К сожалению, поскольку компилятор обрабатывает файлы один за другим, он обычно не замечает, что шаблон определен как экспортируемый в нескольких исходных файлах. В результате подобного недосмотра может произойти следующее:
 при редактировании связей возникает ошибка, показывающая, что шаблон функции определен более, чем в одном файле;
 компилятор несколько раз конкретизирует шаблон функции с одним и тем же множеством аргументов, что приводит к ошибке повторного определения функции при связывании программы;
 компилятор может конкретизировать шаблон с помощью одного из его экспортированных определений, игнорируя все остальные.
 Нельзя с уверенностью утверждать, что наличие в программе нескольких экспортируемых определений шаблона функции обязательно вызовет ошибку. При организации программы надо быть внимательным и следить за тем, чтобы подобные определения размещались только в одном исходном файле.
 Модель с разделением позволяет отделить интерфейс шаблонов функций от его реализации и организовать программу так, что интерфейсы всех шаблонов помещаются в заголовочные файлы, а реализации – в файлы с исходным текстом. Однако не все компиляторы поддерживают такую модель, а те, которые поддерживают, не всегда делают это правильно: модель с разделением требует более изощренной среды программирования, которая доступна не во всех реализациях C++. (В другой нашей книге, “Inside C++ Object Model”, описан механизм конкретизации шаблонов, поддержанный в одной из реализаций C++, а именно в компиляторе Edison Design Group.)
 Поскольку приводимые нами примеры работы с шаблонами невелики и поскольку мы хотим, чтобы они компилировались максимально большим числом компиляторов, мы ограничились использованием модели с включением.
 10.5.3. Явные объявления конкретизации
 При использовании модели с включением определение шаблона функций включается в каждый исходный файл, где встречается конкретизация этого шаблона. Мы отмечали, что, хотя неизвестно, где и когда понадобится шаблон функции, программа должна вести себя так, как будто экземпляр шаблона для данного множества аргументов конкретизирован ровно один раз. В действительности некоторые компиляторы (особенно старые) конкретизируют шаблон функции с данным множеством аргументов шаблона неоднократно. В рамках этой модели для использования на этапе сборки или на одной из предшествующих ей стадий выбирается один из конкретизированных экземпляров, а остальные игнорируются.
 Результат работы программы не зависит от того, сколько раз конкретизировался шаблон: в конечном итоге используется лишь один экземпляр. Но если приложение состоит из большого числа файлов, то время компиляции приложения заметно возрастает.
 Подобные проблемы, характерные для старых компиляторов, затрудняли использование шаблонов. Поэтому в стандарте C++ введено понятие явного объявления конкретизации, помогающее программисту управлять моментом, когда конкретизация происходит.
 В явном объявлении конкретизации за ключевым словом template идет объявление шаблона функции, в котором его аргументы указаны явно. Рассмотрим шаблон sum(int*, int):
 template
  Type sum( Type op1, Type op2 ) { /* ... */ }
 
 // явное объявление конкретизации
 template int* sum< int* >( int*, int );
 Здесь в качестве аргумента явно задается int*. Явное объявление конкретизации с одним и тем же множеством аргументов шаблона может встречаться в программе не более одного раза.
 Определение шаблона функции должно находиться в том же файле, где и явное объявление конкретизации. Если же его не видно, то явное объявление приводит к ошибке:
 #include
 
 template
  Type sum( Type op1, int op2 ); // только объявление
 
 // определяем typedef для vector< int >
 typedef vector< int > VI;
 
 // ошибка: sum() не определен
 template VI sum< VI >( VI , int );
 Если в некотором исходном файле встречается явное объявление конкретизации, то что произойдет в других файлах, где используется такая же конкретизация шаблона функции? Как сказать компилятору, что явное объявление находится в другом файле и что при использовании в этом файле шаблон конкретизировать не надо?
 Явные объявления конкретизации используются в сочетании с опцией компилятора, которая подавляет неявную конкретизацию шаблонов. Название опции в разных компиляторах различно. Например, в VisualAge for C++ для Windows версии 3.5 фирмы IBM эта опция называется /ft-. Если приложение компилируется с данной опцией, то компилятор предполагает, что шаблоны будут конкретизироваться явно, и не выполняет автоматической конкретизации.
 Разумеется, если мы не включили в программу явного объявления конкретизации для некоторого шаблона, но задали опцию /ft-, то при сборке произойдет ошибка из-за того, что функция не была конкретизирована.
 Упражнение 10.8
 Назовите две модели компиляции шаблонов, поддерживаемые в C++. Объясните, как организуются определения шаблонов функций в каждой модели.
 Упражнение 10.9
 Пусть дано следующее определение шаблона функции sum():
 template
  Type sum( Type op1, char op2 );
  Как записать явное объявление конкретизации этого шаблона с аргументом типа string?
 10.6. Явная специализация шаблона А
 Не всегда удается написать шаблон функции, который годился бы для всех возможных типов, с которыми он может быть конкретизирован. В некоторых случаях имеется специальная информация о типе, позволяющая написать более эффективную функцию, чем конкретизированная по шаблону. А иногда общее определение, предоставляемое шаблоном, для некоторого типа просто не работает. Рассмотрим, например, следующее определение шаблона функции max():
 // обобщенное определение шаблона
 template
  T max( T t1, T t2 ) {
  return ( t1 > t2 ? t1 : t2 );
 }
 Когда этот шаблон конкретизируется с аргументом типа const char*, то обобщенное определение оказывается семантически некорректным, если мы интерпретируем каждый аргумент как строку символов в смысле языка C, а не как указатель на символ. В этом случае необходимо предоставить специализированное определение для конкретизации шаблона.
 Явное определение специализации – это такое определение, в котором за ключевым словом template следует пара угловых скобок <>, а за ними – определение специализированного шаблона. Здесь указывается имя шаблона, аргументы, для которых он специализируется, список параметров функции и ее тело. В следующем примере для max(const char*, const char*) определена явная специализация:
 #include
 
 // явная специализация для const char*:
 // имеет приоритет над конкретизацией шаблона
 // по обобщенному определению
 
 typedef const char *PCC;
 template<> PCC max< PCC >( PCC s1, PCC s2 ) {
  return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
 Поскольку имеется явная специализация, шаблон не будет конкретизирован с типом const char* при вызове в программе функции max(const char*, const char*). При любом обращении к max() с двумя аргументами типа const char* работает специализированное определение. Для любых других обращений функция сначала конкретизируется по обобщенному определению шаблона, а затем вызывается. Вот как это выглядит:
 #include
 
 // здесь должно быть определение шаблона функции max()
 // и его специализации для аргументов const char*
 
 int main() {
  // вызов конкретизированной функции: int max< int >( int, int );
  int i = max( 10, 5 );
 
  // вызов явной специализации:
  // const char* max< const char* >( const char*, const char* );
  const char *p = max( "hello", "world" );
 
  cout << "i: " << i << " p: " << p << endl;
  return 0;
 }
 Можно объявлять явную специализацию шаблона функции, не определяя ее. Например, для функции max(const char*, const char*) она объявляется так:
 // объявление явной специализации шаблона функции
 template< > PCC max< PCC >( PCC, PCC );
 При объявлении или определении явной специализации шаблона функции нельзя опускать слово template и следующую за ним пару скобок <>. Кроме того, в объявлении специализации обязательно должен быть список параметров функции:
 // ошибка: неправильные объявления специализации
 
 // отсутствует template<>
 PCC max< PCC >( PCC, PCC );
 
 // отсутствует список параметров
 template<> PCC max< PCC >;
 Однако здесь можно опускать задание аргументов шаблона, если они выводятся из формальных параметров функции:
 // правильно: аргумент шаблона const char* выводится из типов параметров
 template<> PCC max( PCC, PCC );
 В следующем примере шаблон функции sum() явно специализирован:
 template
  T1 sum( T2 op1, T3 op2 );
 
 // объявления явных специализаций
 
 // ошибка: аргумент шаблона для T1 не может быть выведен;
 // он должен быть задан явно
 template<> double sum( float, float );
 
 // правильно: аргумент для T1 задан явно,
 // T2 и T3 выводятся и оказываются равными float
 template<> double sum( float, float );
 
 // правильно: все аргументы заданы явно
 template<> int sum( char, char );
 Пропуск части template<> в объявлении явной специализации не всегда является ошибкой. Например:
 // обобщенное определение шаблона
 template
  T max( T t1, T t2 ) { /* ... */ }
 
 // правильно: обычное объявление функции
 const char* max( const char*, const char*);
 Однако эта инструкция не является специализацией шаблона функции. Здесь просто объявляется обычная функция с типом возвращаемого значения и списком параметров, которые соответствуют полученным при конкретизации шаблона. Объявление обычной функции, являющееся конкретизацией шаблона, не считается ошибкой.
 Так почему бы просто не объявить обычную функцию? Как было показано в разделе 10.3, для преобразования фактического аргумента функции, конкретизированной по шаблону, в соответствующий формальный параметр в случае, когда этот аргумент принимает участие в выводе аргумента шаблона, может быть применено лишь ограниченное множество преобразований типов. Точно так же обстоит дело и в ситуации, когда шаблон функции специализируется явно: к фактическим аргументам функции при этом тоже применимо лишь ограниченное множество преобразований. Явные специализации не помогают обойти соответствующие ограничения. Если мы хотим выйти за их пределы, то должны определить обычную функцию вместо специализации шаблона. (В разделе 10.8 этот вопрос рассматривается более подробно; там же показано, как работает разрешение перегруженной функции для вызова, который соответствует как обычной функции, так и экземпляру, конкретизированному из шаблона.)
 Явную специализацию можно объявлять даже тогда, когда специализируемый шаблон объявлен, но не определен. В предыдущем примере шаблон функции sum() лишь объявлен к моменту специализации. Хотя определение шаблона не обязательно, объявление все же требуется. То, что sum() – шаблон, должно быть известно до того, как это имя может быть специализировано.
 Такое объявление должно быть видимо до его использования в исходном файле. Например:
 #include
 #include
 
 // обобщенное определение шаблона
 template
  T max( T t1, T t2 ) { /* ... */ }
 
 int main() {
  // конкретизация функции
  // const char* max< const char* >( const char*, const char* );
  const char *p = max( "hello", "world" );
 
  cout << "p: " << p << endl;
  return 0;
 }
 
 // некорректная программа: явная специализация const char *:
 // имеет приоритет над обобщенным определением шаблона
 typedef const char *PCC;
 template<> PCC max< PCC >(PCC s1, PCC s2 ) { /* ... */ }
 В предыдущем примере конкретизация max(const char*, const char*) предшествует объявлению явной специализации. Поэтому компилятор имеет право предположить, что функция должна быть конкретизирована по обобщенному определению шаблона. Однако в программе не может одновременно существовать явная специализация и экземпляр, конкретизированный по тому же шаблону с тем же множеством аргументов. Когда в исходном файле после конкретизации встречается явная специализация max(const char*, const char*), компилятор выдает сообщение об ошибке.
 Если программа состоит из нескольких файлов, то объявление явной специализации шаблона должно быть видимо в каждом файле, в котором она используется. Не разрешается в одних файлах конкретизировать шаблон функции по обобщенному определению, а в других специализировать с тем же множеством аргументов. Рассмотрим следующий пример:
 // --------- max.h -------
 // обобщенное определение шаблона
 template
  Type max( Type t1, Type t2 ) { /* ... */ }
 
 // --------- File1.C -------
 #include
 #include "max.h"
 void another();
 
 int main() {
  // конкретизация функции
  // const char* max< const char* >( const char*, const char* );
  const char *p = max( "hello", "world" );
 
  cout << "p: " << p << endl;
  another();
 
  return 0;
 }
 
 // --------- File2.C -------
 #include
 #include
 #include "max.h"
 
 // явная специализация шаблона для const char*
 typedef const char *PCC;
 template<> PCC max< PCC >( PCC s1, PCC s2 ) { /* ... */ }
 
 void another() {
 
  // явная специализация
  // const char* max< const char* >( const char*, const char* );
  const char *p = max( "hi", "again" );
 
  cout << " p: " << p << endl;
 
  return 0;
 }
 Эта программа состоит из двух файлов. В файле File1.C нет объявления явной специализации max(const char*, const char*). Вместо этого шаблон функции конкретизируется из обобщенного определения. В файле File2.C объявлена явная специализация, и при обращении к max("hi", "again") именно она и вызывается. Поскольку в одной и той же программе функция max(const char*, const char*) то конкретизируется по шаблону, то специализируется явно, компилятор считает программу некорректной. Для исправления этого объявление явной специализации шаблона должно предшествовать вызову функции max(const char*, const char*) в файле File1.C.
 Чтобы избежать таких ошибок и гарантировать, что объявление явной специализации шаблона max(const char*, const char*) внесено в каждый файл, где используется шаблон функции max() с аргументами типа const char*, это объявление следует поместить в заголовочный файл "max.h" и включать его во все исходные файлы, в которых используется шаблон max():
 // --------- max.h -------
 // обобщенное определение шаблона
 template
  Type max( Type t1, Type t2 ) { /* ... */ }
 
 // объявление явной специализации шаблона для const char*
 typedef const char *PCC;
 template<> PCC max< PCC >( PCC s1, PCC s2 );
 
 // --------- File1.C -------
 #include
 #include "max.h"
 void another();
 
 int main() {
  // специализация
  // const char* max< const char* >( const char*, const char* );
  const char *p = max( "hello", "world" );
 
  // ....
 }
 Упражнение 10.10
 Определите шаблон функции count() для подсчета числа появлений некоторого значения в массиве. Напишите вызывающую программу. Последовательно передайте в ней массив значений типа double, int и сhar. Напишите специализированный экземпляр шаблона count() для обработки строк.
 10.7. Перегрузка шаблонов функций А
 Шаблон функции может быть перегружен. В следующем примере есть три перегруженных объявления для шаблона min():
 // определение шаблона класса Array
 // (см. раздел 2.4)
 
 template
  class Array( /* ... */ };
 
 // три объявления шаблона функции min()
 
 template
  Type min( const Array&, int ); // #1
 

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

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