Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 87
Текст из файла (страница 87)
Рассмотренная техника программирования, препятствующая разбуханию кода, доказала свою эффективность на практике. Люди, не применяющие этой техники программирования (будь-то в языке С++, или в каком-либо ином языке с аналогичными средствами параметризации), быстро обнаруживают, что излишне продублированный код может запросто потянуть на десятки мегабайт даже в программах весьма умеренного размера. Из-за того, что отсутствует необходимость в компиляции этого избыточного кода, общее время компиляции и компоновки может значительно сократиться.
Применим единственную специализацию для любых списков указателей в качестве еше одного примера минимизации общего кода за счет увеличения доли разделяемого (общего для разных типов) кода. Общий шаблон должен быть объявлен до любой его специализации. Например: (етр1а(е<с!азз Т> с1ат ЕЕ((<Т'> ) l* ... */ ) ( (етр1а(е<с!азз Т> с!ат Е(з( ) l* ... *! ) ( д е(тог: общий шаблон после специализации Критически важной информацией в определении общего шаблона является набор параметров шаблона, которые пользователь должен предоставлять, используя этот шаблон или его специализации. Поэтому одного лишь обьноленил общего шаблона достаточно для объявления или определения специализации: (етр!а(е<с(азз Т> с1азз Е!з(( (етр1а(е<с1азз Т> с!азз Е!з(<Т*> ) /* ...
*/ ) ( Если общий шаблон используется в клиентском коде, он, естественно, должен быть где-то определен (в)3.7). Специализация должна находиться в области видимости кода, в котором пользователь использует эту специализацию. Например: (етр1а(е<с!азз Т> с1азз Е(з( ( l* ... */ ) ( Е(з(<(нг'> 1!( (етр(а(е<с(азз Т> с(ат Е(з(<Т*> ( /* ...
*/ ) ( Уеггог Здесь ЕЫ был специализирован под (а(* позже того, как с помощью объявляющей конструкции Е!з(<1нг'> был уже использован. Все специализации шаблона должны объявляться в том же самом пространстве имен, что и общий шаблон. Если специализация используется, то она должна быть где-то явным образом определена (В)3.7). Другими словами, явная специа- Глава 13. Шаблоны 420 лизация шаблона означает, что автоматическая генерация ее определения не вы- полняется. 13.3.1. Порядок специализаций Одна специализация считается более специализированной (более специфической, узкой), чем другая специализация, если список фактических аргументов шаблона, удовлетворяющий ее патгерну специализации, также соответствует и паттерну второй специализации, но не наоборот.
Например: 77 общий шаблон 7 специализация для любых указателей // специализация для «ои(* !етр(а!е<с(азз Т> с(азз Уес!огз гетр!а!е<с1азз Т> сгазз Уетог<Т*>; гетр!а!е<> с1азз Уес!ог<«оЫ*>; Любой тип может использоваться как аргумент для самого общего шаблона Уес!ог, но только указатели подходят для специализации Уес!ог<Т'>, и только тип «ЬЫ* — для специализации Уес!ог<«оЫ* >.
Более специальным версиям отдается предпочтение в объявлениях объектов, указателей и т.д. (в13.5), а также при разрешении перегрузки (В13.3.2). Паттерн специализации может быть специфицирован в терминах типов с применением конструкций, допустимых при выводе параметра шаблона (в13.3.1, эС.13.4). 13.3.2. Специализация шаблонов функций !етр!а!е<с1азз Т> Ьоо! 1езз (Т а, Т Ь) (ге!игп а<Ь! ) !етр(осе<с(азз Т> «о!д зон(Уесгог<Т>ь «) ( сопл! з(зе ! и = «.
з(хе ( ); уог (гп! еар=п!2з 0<сарг яар l= 2) 7ог(тг (=вар; з<п; 1ее) /ог (т!7ы1-яарз 0<7'! 1 -= оар) !у(1езз(«МИар) ° «()1) ) яшар(«(1], «(1'-аар) ); е!зе Ьгеай! ) Это не улучшает сам алгоритм, а лишь его реализацию. В том виде, в каком шаблон золт() сейчас определен, он не сможет корректно сортировать тип 1ес!ог<сйаг*>, поскольку операция < будет сравнивать два указателя на с!за«, то есть сравнивать адреса первых символов каждой строки. Нам же нужно, чтобы сравнивались сами символы.
Простая специализация функционального шаблона 1езз() для типа сопл! сйаг* позаботится об этом: Естественно, что специализация полезна и для шаблонов функций. Рассмотрим сортировку Шелла из З7.7 и В13.3. В ней производится сравнение элементов операцией < и в деталях выполняется перестановка элементов. Предпочтительнее дать следующее определение: 421 13.5. Специализация <етр<а<е<> Ьоо! 1евв<сопв< слег" > (солса сдпг* а, сопл< сваг* Ь) ( ге<ига в<гетр (а, Ь) <О< ) Как и для классовых шаблонов (В13.5), для функциональных шаблонов префикс <етр!а<е<> означает специализацию, не нуждающуюся в параметрах шаблона.
Конструкция <соне< сйаг*> после имени функции означает, что эта специализация должна использоваться в случае, когда фактические параметры вызова имеют тип сопт< с!<аг*. Поскольку параметры функциональных шаблонов могут быть выведены из фактических параметров вызова, нет нул(ды специфицировать их явно. Поэтому мы можем упростить определение специализации: <етр!а<е<> Ьоо1!ет<> (солса слог* а, сопя< сваг* Ь) ( ге<агп <агстр (а, Ь) <О< ) При наличии префикса <еп<р1а<е<> присутствие вторых пустых угловых скобок излише, поэтому лучше писать так: <етр1а<е<> Ьоо! 1ет (сопя< сваг* а, сопя! слог* Ь) ( ге<ига <агстр (а, Ь) <О < ) Во многих случаях перегрузка 613.3.2) является альтернативой специализации.
Рассмотрим очевидное определение для виар (): <етр<а<е<с<авв Т> гоЫ легар (Ть х, Та у) ( Т < = х«у коянруем х во временную переменную х = у< // копируем у в х у = Ы й копируем временную переменную в у ) Такое решение неэффективно для типа Уес<ог, поскольку при этом будут копироваться все элементы векторов. Элементы х будут даже копироваться дважды. Проблема решается предоставлением версии в)еар(), более подходящей для векторов. Объект типа Уев<ос будет представлен только данными, достаточными для косвенного доступа к элементам (как 52г(пО; В11.12, В13.2).
Таким образом, работу впар() можно свести к обмену этими представлениями. Для этого я определяю впар () как функцию-член шаблона Уес<ог (913.5); дменяемся представлениями <етр!а<е<с!ат Т> уо!«Уев<ос< Т>:: виар ( Усе<ага а) впар (г, а. г) < вп ар (вс, а . ес ) < ) Теперь эту функцию-член можно использовать в качестве альтернативы общему шаблону як<ар(): Глава 13. Шаблоны 422 (етр(а(е<с(азз Т> го((( ивар ( Уес(ог<Т> ь а, Уес1ог<Т' з Ь) ( а.зыар(Ь) ' Рассмотренные здесь специализация!езз() и перегруженная версия з)вар() используются в стандартной библиотеке (816.3.9, 820.3.!б).
Кроме того, они отлично иллюстрируют широко применяемые приемы программирования. Специализация и перегрузка полезны тогда, когда имеется более эффективная альтернатива общему алгоритму для конкретных аргументов шаблона (здесь это з)вар ( ) ). Кроме того, специализация отлично работает тогда, когда виррегулярность» типа аргумента приводит к неправильной работе общего алгоритма (злесь это !езз () ). Часто такими «иррегулярными» типами являются встроенные типы указателей и массивов.
13.6. Наследование и шаблоны Шаблоны и наследование являются механизмами построения новых типов из уже существующих, а также помогают писать код, извлекающий пользу из самых разных форм общности. Как показано в 83.7.1, 83.8.5 и 813.5, комбинация этих двух механизмов является основой для многих полезных технологий программирования.
Наследование классового шаблона от нешаблонного класса позволяет реализовать общую реализацию для множества шаблонов. Вектор из 813.5 служит хорошим примером: (етр(а(е<с(азз Т> с(азз Уесюг<Т*>: рг(иа(е Уетог<»од(*> ( /* ... */ ) ( Под другим углом зрения этот пример иллюстрирует тот факт, что шаблон используется для предоставления элегантного и типобезопасного интерфейса к средству, в противном случае являющегося и опасным, и неудобным.
Естественно, часто бывает полезным создавать классовые шаблоны, производные от других классовых шаблонов. Базовый класс, в частности, служит строительным блоком в реализации производных классов. Если члены базового класса должны соответствовать тому же параметру, что и члены производного класса, то имя базового класса следует соответственно параметризовать; хорошим примером служит Уес из 43.7.2: (енгр!а1е<с!ит Т> с(ат вес(ог ( l* ... *l ] ( (етр(а(в<с(азз Т. с(азз Уес: риЬ((с нес(ог<Т> ( /* ...
*! ); Правила разрешения перегрузки шаблонных функций гарантируют, что функции работают правильно для таких производных типов ®13.3.2). Случай, когда произволный и базовый классы имеют один и тот же параметр шаблона, является наиболее распространенным, но это не обязательное требование. В редких и интересных случаях в качестве параметра базового класса указывается сам производный класс. Например: ( базовые операции над капп(ейнерол~и (етр(а(с<с(азз С> с(ат Ваз(с орз ( риЬПс: Ьоо(прего(ог== (соаз( Сз) соне(; 1! сравнивает все >ведено(ы 13.6, Наследование и шаблоны 423 Ьоо< орега<ог! = (сопя! Са ) сопи; р ... У доступ к операциям тило С; соля! Са Иепге<<() сопя! (ге<игл я<аис саз«сопя< Са> (*<а<я) ! ) )< <етрд<<е<с<аяя Т> с!аз!Мося соп<атег: риЬВс Вез(с ар!<Ма<В сои<а(лег<Т» ( риЫ<с: я<ее < я<ее() сопя<< Та орега<ог[] (зйе !) ! сопя< Та орепиог() (я<ее !) сопи; У...
)< Это позволяет один раз определить базовые операции над контейнерами и отделить их от определений самих контейнеров. В то же время, поскольку определение операций вроде == и . = должно выражаться в терминах и контейнера, и его элементов, то тип элементов должен передаваться шаблону контейнера. Полагая, что Ма<В сои<а<пег аналогичен традиционному вектору, определения членов Ваз(с орз будут выглядеть примерно так: <етр<а<е<с1ат С> Ьоо< Вазк ар!<С>: горе!от!== (соляа Са а) соля< ( (1 («ее<ге«() .