Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 86
Текст из файла (страница 86)
Здесь присутствуют три концепции: строка, тип элементов и критерий, используемый сортируюшим алгоритмом для сравнения элементов строки. Мы не можем встроить критерий сортировки непосредственно в контейнер, ибо контейнер не должен навязывать свои проблемы типам элементов. Мы не можем также встраивать критерии и в типы элементов, ибо существует много разных критериев сортировки.
Таким образом, критерий сортировки не встраивается ни в контейнер, ни в тип элементов. Критерий сортировки нужно указывать лишь в тот момент, когда возникла нужда в выполнении конкретной операции. Например, какими критериями сортировки нужно воспользоваться в случае строк, содержащих имена шведов? В этом случае могут используются две разные сортируюшие последовательности [два разных способа нумерации символов). Ясно, что ни общий строковый тип, ни общий алгоритм сортировки не должны ничего знать про способы упорядочения шведских имен. Поэтому любое общее решение нуждается в том, чтобы сортируюший алгоритм формулировался в терминах, допускающих настройку не только по типам, но и по вариантам использования этих типов.
В качестве примера рассмотрим обобщение стандартной библиотечной функции ай спер () для строк элементов типа Т 513.2): 13.4. Применение аргументов шаблона 415 ге<ип< в<г1 . (еп8<й () -в<г2. 1епа<й () ) Если потребуется, чтобы сотраге ( ) игнорировала регистр букв, или действовала в соответствии со специфическими национальными настройками и т.п., то тогда нужно определить подходящий вариант С:: е9() и С«11() . Это позволяет выражать любой алгоритм (сравнение, сортировку и т.п.) в терминах «С-операций» и контейнеров. Например: <етр1а<е<с!авв Т> с1авв Стр Б'обычное (умолчательное) сравнение риЫ<с: малс!а<ее(Та, ТЬ) (ге<игл а==Ь< ) в<а<!с !п< «(Та, ТЬ) (ге<игп а<Ь< ) )1 й сравнение шведских имен по литературным правилам с<иве ййега<е ( риЫ<с: Маце <и< Ее (сйаг а, сйаг Ь) ( ге<ига а==Ь< ) Малс !п< й(сйаг,сйаг) 1 о'поиск в таблице по коду символа (5!3,9!14)) )1 Теперь мы можем выбирать варианты поведения кода (зависящие в данном случае от правил сравнения строк) явным заданием аргументов шаблона: го!<!2'(Б<нпд<сйаг> вшейе1, 31<!пя<сйаг> в<ге<)е2) ( сотраге<сйаг, Стр<сйаг» (в<ее<(е1, вне<!е2) 1 сотраге<сйаг, Прего<в> (лиеИе1, вне<(е2) 1 Передача операций сравнения в качестве аргумента шаблона имеет два существенных преимушества перед альтернативными решениями, например, перед передачей указателей на функции.
Во-первых, можно передать несколько операций в качестве единственного аргумента без дополнительных затрат на выполнение кода. Кроме того, операции сравнения е9() и 11() легко встраиваются, в то время как встраивание вызова функции по указателю на нее является довольно сложной задачей для компилятора. Естественно, что операции сравнения можно реализовать и для встроенных типов, и для пользовательских типов. Это важно для построения обобщенных алгоритмов, работающих с типами, обладающими нетривиальными критериями сравнения (см. 818.4). Каждый генерируемый из шаблона класс получает копию каждого статического члена классового шаблона (см.
5С.13.1). 13.4.1. Параметры шаблонов по умолчанию Необходимость явного задания критерия сравнения при каждом вызове функции несколько утомительна. По счастью, в общем случае можно опереться на умолчательные варианты критериев, а в редких случаях явно задавать их более специфические варианты. Это можно реализовать с помощью перегрузки: Глава 13. Шаблоны 416 гетр(а(е<с1азз Т, с1азз С> тг сотраге(солмБп(ля<Т>ь зс1, солзз Яачщ<Т>ь ззг2); УУ<равливаем, используя С 1етрйле<с1азз Т> ол сотраге (сопм Б~ппя<Т>ь згг1, сопя( Яп(пя<Т>ь ззг2); Рсравливаем, используя Стр<Т> По-другому, обычный вариант критерия сравнения можно указать в качестве аргумента шаблона по умолчанию; 1етр1аге<с!азз Т, с(азз С = Стр<Т» (пз <отроге (сопзз Ягг(ля< Т> ь за 1, соле( Яг(ля< Т> ь ззг2) Тог((пг 1=0; 1<тг1.1епагй () ьь 1< згг2.1елягй (); (««) (/'(! С: .
"еа (згг1 [1], згг2 [1) ) ) ге(игл С:: И (тг1 [1], ззг2 [1] ) 2 -1: 11 ге(игл ззг1 .1епязй () -ззг2.1епягй () Теперь можно писать так: гойе(Яггпя<сйаг> звейе1, 5(г1ля<сйаг> зи~егге2) ( сотраге (зи ейе1; звейе2); сотраге<сйаг, 12(егаге> (зи еЫе1, зи егге2); ) УУ используется Стр<сйаг> УУ используется 11(ега(е Или для менее экзотического (по сравнению со шведскими фамилиями) сравнения с учетом и без учета регистра букв: с1азз )чо сазе ( У* ...
*У ); гоЫ Т(81ггпя<сйаг> з1, Бзггпд<сйаг> з2) со»праге (з1, з2); сотраге<сйаг, ]»о сазе> (з1, з2); ) УУучитываем регистр УУ яе учитываем регистр Технология, позволяющая осуществлять задание вариантов поведения кода через аргументы шаблонов с использованием их умолчательных значений для наиболее общих случаев, широко применяется в стандартной библиотеке 518.4). Достаточно странно, но она не используется для типа Ьаз(с ззг)пя (8]3.2, глава 20), Параметры шаблонов, применяемые для задания вариантов поведения кода, часто называют'«свойствами» ((гайз), Например, строки стандартной библиотеки используют сйаг а а1(з (в20.2.[), стандартные алгоритмы полагаются на соответствующие свойства итераторов (5]9.2.2), а контейнеры стандартной библиотеки — на аллолаторы (а11оса(огз) (8]9.4).
Проверка семантики умолчательного значения параметра шаблона проводится только в случае его использования. Например, пока мы воздерживаемся от использования умолчательного значения Стр<Т>, мы можем использовать функцию сотраге () для сравнения строк элементов типа Х, для которых Стр<Х> не компилируется (например, потому что операция < не определена для типа Л).
Это очень важно для стандартных контейнеров, применяющих умолчательные значения аргументов шаблона (5 [6.3.4). )3.5 Специализация 417 1 3.5. Специализация По умолчанию, шаблон является единственным определением, которое должно использоваться для всех конкретных аргументов шаблона, задаваемых пользователем. Это не всегда оптимально для разработчика шаблона. Для него часто актуально рассуждать так: «если аргумент шаблона будет указателем, нужно применить вот эту реализацию, а если нет — то другую реализацию» или «в случае, когда аргумент шаблона не является указателем на класс, производный от Му Ьаяе, выдать сообшение об ошибке». Такие проблемы можно решить, обеспечив альтернативные определения шаблонов н заставив компилятор выбирать нужный вариант на основе аргументов шаблона, указанных при его использовании. Такие альтернативные определения шаблона называются специализациями, определяемыми пользовоглелем (ияег-де[1 лед ярес!а1!хапопя), или просто пользовагпельскими специализациями (ияег зрес!а!!еа!(опв) '.
Рассмотрим типичные варианты применения шаблона Уес!ож // телега! тес(ог (уре гетр(а!с<с!аяя Т> с)авв Уесгог Т* г; !пт яю рииич Уестог ( ); ехрдсй Уестог (1п!) ! Ть е(ещ (шт() (ге!ига »[1); ) Ть орегаюг [1 (!а!(); гоЫ ли ар ( Уестогь ); /.. )' Уестог<!пт> Ы; Уестог<5!заре* > гря; Уестог<ягг!пд> гя; Уестог<спаг*> грс! уесгог<)уо«(е* > грп ! Большинство векторов строятся на указателях некоторого типа. На то сушествует ряд причин, но главная заключается в том, что только указатели обеспечивают полнморфное поведение на этапе выполнения (В2.5.4, В12.2.6). В итоге, любой программист, практикуюшнй объектно-ориентированное программирование и использующий безопасные по отношению к типам контейнеры (такие как контейнеры стандартной библиотеки), так или иначе приходит к контейнерам указателей.
В большинстве реализаций С++ код шаблонов функций реплицнруется. Это хорошо с точки зрения производительности, но при недостаточной осторожности может привести к разбуханию кода в критических случаях, как в случае шаблона Уес!ог. | Еще чаще нх называют просто специализациями. — Прим. ред. 418 Глава (3. Шаблоны По счастью, имеется очевидное решение. Все контейнеры указателей могут разделить единственную специальную реализацию, называемую специализацией (крес1айда)1ои). Определяем специфическую версию (специализацию) шаблона Уее)ог для указателей на юЫ: <етр!а<к<> с1акк )есюг<чоЫ*> юЫ** р; ))' ...
го<а*к орега)ог[) (!и« ) < Эта специализация может затем использоваться как общая реализация для всех векторов указателей. Префикс <еикр!а<е<> говорит о том, что определяется специализация, не нуждающаяся в параметрах шаблона. Аргументы шаблона, для которых эта специализация должна использоваться, указываются в угловых скобках после имени.
Таким образом, <юЫ*> означает, что данное определение должно использоваться в качестве реализации для всех Уес<ог, у которых Тесть юЫ*. Определение Уес<ог< юЫ* > называется полной специализацией (сотр!е)е крее<а!!еаВоп)„поскольку параметры шаблона отсутствуют. Оно используется в клиентском коде следующим образом: Уес)о«чоЫ*> чрч< Для того чтобы определить специализацию, которую можно использовать для любого вектора указателей (и только для векторов указателей), требуется частичная специализация (ранца! крее(айка)<оп): <етр1а)е<г)акк Т> сйт Уес)ог<Т*>: рычаге Уес<ог<чоЫ*> ( риЫ<с < <уре<)е/ Уес<ог<чоЫ*> Ват) Уес<ог() ( ) ехр1!сй Уес<ог(<п<!): Ваке(!) () Т*к е1е<н (<п«) (ге<игн гет<егрге< са<а<Т*к> (Ваке::е1ет (1) ); ) Т*к орега<ог[) (<п« ) (ге)игп гет<егрге< сак«Т*к> (Ваке: <орега)ог() (<) ) < ) ))'...
)< Паттерн Гсхема, форма) специализации (крег!а!!аа)!оп рапегп) в виде <Т'> после имени означает, что специализация используется для любого типа указателей; то есть это определение используется всегда, когда указывается аргумент шаблона в виде Т*. Например: Уес<ог<Я)<яре*> чрк; ь' <Т*> егть <Яареь>, так что Т есть Б)<аре уес)ог<т)**> чрр1) ) <Ть есть <!и<**>, так что Тетпь !н<ь Отметим, что когда используется частичная специализация, параметр шаблона выводится из паттерна специализации, так что в этом случае параметр шаблона не совпалет с фактическим аргументом шаблона.
В частности, для Уее)ог<Я~аре*> параметр Тесть Кйаре, а не Бйаре*. 13.5. Специализация 419 Определив рассмотренную частичную специализацию для шаблона Кес(ог, мы получили общую реализацию для любых векторов указателей. При этом по сути дела 1(ес(ог<Т'> является интерфейсом к )(ес(ог<гой)" >, реализованным через наследование и оптимизируемым за счет встраивания. Важно, что все эти усовершенствования реализации Гес(ог выполнены без изменения интерфейса пользователя. Специализация как раз и задумана как альтернативная реализация для специфических случаев использования одного и того же пользовательского интерфейса. Естественно, можно назначить разные имена общему шаблону (вектору вообще) и специализации (вектору указателей), но это будет только путать пользователей, и многие из них могут и не воспользоваться специализацией для векторов указателей, что лишь раздует суммарный объем их кода. Гораздо лучше скрывать важные детали реализации за общим интерфейсом.