Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 213
Текст из файла (страница 213)
В частности, можно воспользоваться явным инстанцированием, чтобы получать ошибки компиляции в предсказуемое время, а пе тогда, когда реализация определит, что нужно сгенерировать специализацию. Для некоторых по.льзователей необходим хорошо предсказуемый процесс построения программы. В.13.8. Связывание имен Важно опреде.пить шаблоны функций так, чтобы они имели как можно меньше зависимостей от нелокальной информации. Дело в том, что потолг шаблон будет использоваться для генерирования функций и классов, основываясь на неизвестных типах и в неизвестном контексте. Всякая тонкая зависимость от контекста, скорее всего, всплывет для какого-нибудь п рограммиста в виде проблемы при отладке — и вряд ли он захочет разбираться в деталях реализации шаблона.
К универсальному правилу— 937 В.13.Шаблоны избегать по мере возможности глобальных имен — нужно с особой серьезностью отнестись в коде шаблона. Таким образом мы стараемся сделать определения шаблонов по мере возможности самодостаточными и превратить максимум из того, что в противном случае стало бы глобальным контекстом, в форму параметров шаблона !например, свойств; 5 1ЗА, 9 20.2,1), Однако нужно использовать и некоторые нелокальные имена. В частности, чаше всего пишут набор взаимодействующих между собой шаблонов функций, а не один самодостаточный шаблон. Иногда такие взаимодействующие функции могут быть членами класса, но не всегдю Иногда лучшим решением являются пелокальные функции.
Типичный пример зтого — вызов в функциях яог! () функций яшар () и 1еяв () Я 13 5 2). Много соответствующих примеров предоставляют алгоритмы стандартной библиотеки (глава 18). Другой источник использования нелокальных имен при определении шаблона— операции с общепринятыми именами и смыслом, такие как +, ', П и яог! (). Рассмотрим пример: Ф!пс!ийе<оесГог> Ьоо! !гас!ад; гетр!а!в<с!аяя Т Тяит [яЫ;.вес!от Т ьо) [ Тг= !!, (Г (!гас!пя) сегг «яит (" «с,о « ")~п'; /ог (гп! ! = О; !<евсее [); 1-н-) ! = !" о[!); ге!ига г; и!пс!иг!е<с!иаг!.Ь> оогсЦ [я16 соесгог< г2иаг!>8 о) ( Г)иас! с = яит (о); ) Невинный с виду шаблон функции яит () зависит от оператора +.
В данном приме- ре + определен в <с/иаг!.й>: г)иаг!орегагог+ [!2иаа!, ()иай); Важно то, что когда определяется яит (), в области видимости нет ничего, относящегося к !Еиас!-числам, и нельзя предполагать, что автор функции яит () знает о классе !'„!иас(, В частности, оператор + может быть определен после яит () в тексте программы, и даже позже по времени. 11роцесс поиска объявлений для каждого имени, явно или неявно используемого в шаблоне, называется связыванием имен. Главная проблема со связыванием имен шаблона состои~ в том, что в инстанцировании шаблона замешаны три контекста, которые нельзя четко разделить: [1) контекст определения шаблона; (2) контекст объявления типа аргумента; [3) контекст использования шаблона. 988 Приложение В.
Технические подробности В.13.8.1. Зависимые имена При определении шаблона функции нам нужно убедиться, что контекста достаточно, чтобы определение шаблона имело смысл в терминах его фактических аргументов, не захватывая «случайный» мусор из окружения в точке использования. Чтобы помочь этому, язык разделяет используемые в определении шаблона имена падве категории; (1) Имена, зависящие от аргумента шаблона. Такие имена связываются в некоторой точке инстапцировация Я В.13.8.3). В примере с функцией зищ () определение оператора + можно найти в контексте инстандирования, поскольку она принимает операнды типа аргумента шаблона.
(2) Имена, не зависящие от аргументов'шаблона. Такис имена связываются в точке определения шаблона 18 В.13.8.2). В примере с зит () шаблон пес!ог определен в заголовочном файле <оес!ог>, и ко~да компилятор встречает определение зищ (), логическая переменная !гас!лу находится в области видимости. Простейшим определением «ЛГзависит от параметра шаблона Т» было бы «%является членом Т». К сожалению, этого не вполне достаточно; сложение чисел с квадратичной точностью !~пас! Я В 13.8) является примером обратного. Поэтому говорят, что вызов функции зависигл от аргумента шаблона, если и только если выполняется одно из следующих условий: (1] Тип фактического аргумента зависит от параметра шаблона Т согласно правилам выведения типа Я 13.3.1). Например,!"'(Т(!)), Т(!),Щ(у(!)) иДИ), считая, что ! — это Т.
[2) Вызываемая функция имеет формальный параметр, ко!орый зависит от Т по правилам выведения типа (~ 13.3.1). Например, Т(7), Т(!!в!<Т>Ь) иДсолз! Т"). В основном имя вызываемой функции зависимо, если оно очевидно зависимо при просмотре ее аргументов или формальных параметров. Вызов, который случайно имеет аргумсип соответствующий фактическому типу параметра шаблона, не является зависимым. Например: гетр!осе<с!азз Т> ТЯ(Та) ( ге!иглу(!); // ошибка: в области видгяости ветр!1, ил!!) не зависит от Т оо!дд (!л!); гаге=в (2); Неважно, что при вызове ! (2) типом Т оказался сл1, и аргумент 8'() тоже оказался сл!. Если бы мы рассматривали у (1) как зависимый, его смысл для читающего определение шаблона был бы абсолютно непонятным и таинственным, Если программист хочет, чтобы п(сл!) вызывалась, определение а'(!и!) должно располагаться до определе- нияз(), так чтобы при анализеД) определение и(!л!) было в области видимости.
Это то же самое правило, что и для определения функций, не являющихся шаблонами. В добавление к именам функций нмя переменной, типа, соле! и т. д. может быть зависимым, если их тип зависит от параметра шаблона. Например; гелтр!а!е<с!азз Т> ооЫ !с! (соле! ТК а) ( гурелате Т: Метгуре р = а р, //р и Метгуре зависят от Т сои!«а! «' ' «р->1, ! / ! и! зависят от Т ) 939 В.13. Шаблоны (а! х; гетр!иге<с!авв Т Т/(Та) ( х++; у++; // правильно // слииоха: в области видииости вет у, // и у не зависит от 7 ге!ига а; Ысу; !псе=/Д; Если объявление найдено, это объявление используется, даже если впоследствии может найтись объявление «получше>. Например; гоЫд ЫоиЫе); гетр!асе -с! а ее Т> с!авв Х: риЫЫ Т ( рибйа ооЩ() ( у (2); ) // вызов у(с(оиЫе)! //- )' ооЫ д (Ы!); с!авзХ(); ооЫ Ь (Х<Т> х) х,/(); ) Когда генерируется определение для Х<Х> Я), сс(!и!) не рассматривается, поскольку она объявлена после Х.
Не важно, что Х используется после объявления у (!а!). Также вызов, не являющийся зависимым, нельзя «угонять> в базовый класс: с!авв У(рибйс: иоЫ у(ЫЯ; ); ооЫ б (Х<г'> х) ( х,Д; И снова Х<У>:Я) вызовет у (с(оиб(е). Если бы программист хотел вызвать д () из базо- вого класса Т, об этом следовало сказать в определении Я: В.13.8.2. Связывание в точке определения Когда компилятор видит определение шаблона, он решает, какие имена являются зависимыми (б В.13.8.3).
Если нмя зависимо, поиск его объявления нужно отложить до инстапцирования (ф В,13.8.3). Имена, не зависящие от аргумента шаблона, должны находиться в области видимости (9 4.9.4) в точке определения. Например: Приложение В. Технические подробности 940 1етр!а1е<с!азз Т» с(азз ХХ: риЫЫ Т( ооЫ ! ((( Тсу(2(; ( //вызывает Тяд() О.- Тут, конечно, применяется эмпирическое правило, что определение шаблона должно быль настолько самодостаточно, насколько это возможно. В.13.8.3. Связывание в точке инстанцирования Каждое применение шаблона для данного набора аргументов шаблона определяет точку инстанцирования.
Это точка находится в ближайшей глобальной области ви- димости или области видимости пространства имен, охватывающей се использова- ние, прямо перед объявлением, содержащим это использование. Например: з1гис1Х(Х)!п1(/*,. '/); ооЫя(Х(; 1еьир!а!в<с!азз Т> ооЫДТа((д(а(; ) оо!с! Ь (( ( ех1егп ооЫ я (!п1(, У(2(, //вызов ((Х(2)); эквивалентно(<Х>(Х(2)) Здесь точка инстанцирования для/ находится прямо перед 71 [], так что функция я (,', вызываемая в/'[(, является глобааьной и [Х[, а не локальной д ((п1(.
Определение «точ- ки пнстанцировапияь подразумевает, что параметр шаблона никогда не может быть связан с локальным именем или членом класса. Например: //- ) Также неквалифицированное нмя, используемое в шаблоне, не может быть связано с локальным именем. И наконец, даже если шаблон впервые используется внутри класса используемые в шаблоне неквалифипированные имена нс будут связаны с членами этого класса.
Игнорирование локальных имен существенно для предотвращения многих макросоподобных неприятностей. Например: 1етр!а1е<с!азз Т ооЫ зог1 [оес1ог<Т>6 о( ( зог1 (оЬея!и ((, и епа' (((; // используется зог1() // из стандаргпноб библиотеки // (прочел явно не указывается зЫ::) //элементы. // сортировка элементов ооиЩ ( з)гис1Х(/* ... "/); оесгог<Х> о; с!аез Сота!пег ( оес1ог<1п1> о; риЫЫ ооЫзог1 [( //локальная структура О о!иибка: нельзя в качестве параметра //шаблона использовитьлокальнуюструктуру 941 8.13.
Шаблоны ( свогГ (с[; )) вог!(иесГог чаРЙ) вызывает гда'::вон(), а не Сад!агаегхеог1() ) Пусть вог1 (ьбс1ог Т' Ц вызывает вог1 ([,используя вЫсвог1 ([. Резулы ат был бы тем же, а код был бы прозрачнее. Если точка инстанцирования для шаблона, определенного в пространстве имен, находится в другом пространстве имен, для связывания доступны имена из обоих пространств. Как всегда, для выбора между именами из разных пространств имен используется разрешение перегрузки (9 8.2.9.2).
Отметим, что шаблон, используемый несколько раз с одним и тем же набором аргументов, имеет несколько точек инстанцирования. Если связывания независимых имен различаются, программа неверна. Однако эту ошибку трудно выявить в реализации, особенно если точки инстанцирования находятся в разных единицах трансляции. При связывании имен лучше избегать сложностей, минимизируя применение в шаблонах нелокальных имен н пользуясь заголовочными файлами, чтобы контексты использования были согласованы.