Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 85
Текст из файла (страница 85)
Мы не мол!ем жестко задать критерий сортировки как часть контейнера, потому что контейнер гв общем случае) не может ожидать его наличия в своих элементах. Мы не можем задать критерий сортировки как часть типа элемента, потому что существует много различных способов сортировки элементов. Следовательно, критерий сортировки не встроен ни в контейнер, ни в тип элементов. Вместо этого критерий указывается при выполнении конкретной операции. Например, если у меня есть строки символов, содержащие шведские имена, каким критерием я воспользуюсь для сравнения? Для сравнения шведских имен обычно используются две различных сортирующих последовательности 1способзы нумерации символов).
Естественно, ни общий строковый тип. ни общий алгоритм сортировки не должны знать о соглашениях по сортировке шведских имен. Поэтому любое общее решение нуждается в том, чтобы алгоритм сортировки был выражен в общих терминах, которые могут быть определены не только для какого-то конкретного типа. В качестве примера давайте обобщим функцию стандартной библиотеки С я1гсгпр (( для строк любого типа Т Глава 13. Шаблоны 390 /ог(т1 1=0; 1«вгг!.!епдЕЬ () с о !<всг2!епдЕЬ (); !+») Д!С ец (вгг1[!], вЕг2[!))) ге!игл С !Е (вгг![!), вгг2[Е() 2 — 1; 1; ге1игп в1г! !епдЕЬ () — вгг2.!ела ('„ Гели кто-ннбудь захочет, чтобы сотраге () игнорировала регистр оукв, использовала национальные символы н т.
и., мол«но определить подходяшуео Ссее! () [равпо) и Сс(1 () (меньше чем). Это позволит выразить любые алгоритмы [сравнепие, сортировку и т. п.) в терминах «С-операций» и контейнеров. Например: 1етр!а!е«с1авв Т с1авв Стр( Ообичное сравнение по умолчанию риЬ!ес вЕасес ЕпЕ ед(Т и, Т Ь, '( ге1игп а==Ь; ) вгаЕес тЕ !Е (Т и, Т Ь) ( е е1игп а«Ь, ) с!авв Е.!Еегаге ( //сравнение тпедскил геен по лееепераепурни>е правилом риблс; всавс т1 ее! (слаг а, сдпг Ь) ( ге!иго а==Ь; ) в!а!!с еп1Й (сЬаг, сдаг), // поиск в таблице но основе // «значения> символа (у 139[14)) Теперь мы можем выбирать правила сравнения путсье явного задания аргументов шаблона: оо(еЕ/(31г!пд«сдпг' виеес(е1, 31ггпд«еда~ внес(е2) сотраге«сцаг, Стр<слаг > (втес(е1, виеес(е2) сотраге - сьаг, ТЕЕегаЕе> (втес(е1, втееЕе2), Передача операций сравнения в качестве параметра шаблона имеет два значительных преимушества при сопоставлении с альтернативными подходами, например, передачей указателей на функции.
Можно передать несколько операций в качестве единственного аргумента без дополните.льных затрат во время выполненяя. Кроме того, операторы сравнения ее)() и ЕЕ() легко реализовать в виде встроенных функций, в то время как встраивание вызова функции через указатель требует исключительного внимания со стороны компилятора. Гстественно, операпип сравнения можно реализовать для типов, определяемых пользователем, также как и для встроенных типов. Это является существенным молеентом для реализации обобщенных алгоритмов, работающих с типами с нетривиальнымп крптериямн сравнения [сье.
9 18.4). Каждый генерируемый по шаблону класс получает копию каждого статического члена шаблона класса (см. 9 В.13.1). 13.4.1. Параметры шаблонов по умолчанию Явное задание критерия сравнения при каждом вызове является довольно утомительным занятием. К счастью, можно легко задать умолчание таким образом, что критерий будет явно указываться только для необычных сравнений. Это можно реализовать при помощи механизма перегрузки: 391 (34. Использование аргументов шаблона для выбора алгоритма 1етр!а1е<с1акк Т, с1аяя С> О сривнение с использованием класса С !п1 сот ра ге (сопя! $1г!ау< Т>$ за 1, сопИ $1г(пд< Т>«яй 2), 1етр1а1е<с!акк Т> О сравнение с использованием Стр< Т 1пг сотраге (сопк1 $1г пу< Т ь к1г1, сопя1 $1г!пу<Т й зй'2),' В качестве алы ерпативы мы можем указать < обычный» вариант в качестве аргумен- та пкаблона по умолчанию: 1етр1а1е<с!акк Т, с1акя С= Стр<Т > 1п1 сотраге (сопз1 Б1ппу<Т Ь з1г1, сопИ $1г(пд<Т>2 яй 2) ( уог (!п1! =О, !<ягг! !епутй () й% !<я1г2 !епугй (); 1++) (г (~ Ссец (я1г! [!], Иг2[!])) ге1игп Сей (ксг![(], кгг2[1))? -! .
1, ге1игп кгг!.!епутй () — кгг2.!елей (); Теперь мы можем записать: оо(с! (($1г(пд<сйаг> ятес!е1, Бутпу<сйаг> зтеде2)( сот риге (ятес!е1, язве<!е2) со тра ге <ей а г, Тл1ега 1е> (яме<(е1, ятеа!е2); ) 11 испольвуетсл Стр<гдаг> 11 используетсл !л1ега1е Менее зкзотическнй пример (для не шведов) — зто сравнение обычным способом и без учета регистра: с!аяз Уо саяе ( 1< ...
'! ), иоЫЯ$1г1пц<сйаг> к1, $1г!пу<сйаг> я2) ( сотраге (з1, з2); сотраге<слаг, !уо саяе> (к1, к2); ) О учитываетрегистр 11 не учитывает регистр Выбор алгоритма через аргумент шаблона и задание значения зтого аргумента по умолчанию являются наиболее об!цпм подходом и широко используются в стандартной библиотеке (см., например ф 18А). Довольно странно, но зтн методы не используютгя для сравнений в Ьая!с к1г!пу (() (3.2, глава 20).
Параметры шаблонов, используемые для выбора того или иного алгоритма, часто называют «свойствами > (стай<). Например, строковый класс из стандартной библиотеки использует сйаг 1га!1я (й 20.2А), алгоритмы стандартной библиотеки полагаются на свойства нтераторов (<) (9.2.2), а контейнеры стандартной библиотеки — на свойства распределителей памяти (а(!оса!осе) Я 19А). Проверка семантики аргумента по умолчанию для параметра шаблона осуществляется тогда (и только тогда), когда аргумент по умолчанию действительно используется.
В частности, пока мы воздерживаемся от использования аргумента шаблона по умолчанию Стр<Т>, мы можем сравнивать (сотраге ()) строки типа Х, для которых Стр<Х> не компилируется (например, потому что оператор < не определен для Х). Это является важнейшим моментом при проектировании стандартных контейнеров, которые используют аргументы шаблона для задания значений по умолчаник! Я (6.3А). 392 Глава 13. Шаблоны 13.5.
Специалиэация По умолчанию, шаблон прелоставляет единственное определение, которое должно использоваться лля всех аргументов шаблона (или комбинаций аргументов шаб лона). Это нс всегда имеет смысл для создателя шаблона. Я мог бы захотеть сказать «если аргументом шаблона являешься указатель, используй зту реализа~ппо, если нет — используй ту» или «выдай сообщение об ошибке, если аргументом шаблона не является указатель на объект класса, производного от М!1 базе». Многие подобные проблемы можно решить, обеспечив альтернативные определения шаблона н предоставив компилятору делать выбор нужного варианта на основе аргументов шаблона, указанных при его использовании.
Такие альтернативные определения шаблона называются специакизациями, определяемыми пользовагпелем, или просто пользовательскими специализациями. Рассмотрим возможные непользования шаблона )гес1ог. у! оби1ий пит иес1ог 1егпр1а1е«е1азз Т> «1азе Ъе«1ог( Т' ь, 1п1 зе, риЫ1е. Уеегог ГЗ ехр!ин Уес1ог 11п~, Те. е!егп 11п1 й ( ге1игп и[1); ) Те, оре га 1ог(! !1п1 й иоЫ яшар ( геегогЦ О." Уес1ог«1п1> Ы; гес1ог«36аре'> яре, Чегго~ егггпа> из; тгее1ог<едаг" > ьрс, Ъе«1ог«Мое(е*> ирп; Большинство векторов будут векторами указателей некоторого типа. На то существует несколько причин, главная из которых состоит в сохранении пол иморф ного поведения на зтапс выполнения (3 2,5А, 3 (2.2.6).
То есть любой, кто придерживается объектно-ориентированного программирования н нспользует безопасные с точки зрения типов контейнеры (такие, как контейнеры стандартной библиотеки), придет к множеству контейнеров с указателями. В большинстве реализаций С++ код функций шаблона дублируется. Это положительно сказывается на производительности во время выполнения, но при отсутствии определенных мер предосторожности зто ведет к разбуханию кода в критических случаях, таких как в примере с Фес1ог.
К счастью, имеется оченидное решение. Контейнеры указателей могут совместно пользоваться единственной реализацией. Это можно выразить прн помощи специализации. Для начала определим версию (специализацию) «гес1ог для указателей ца иоЫ: 1етр!а1е е1азе гее1ог.ьо1Й> ( иоЫ'* р; П- 393 13.5. Специализация оо(и!'Ь орега1огЦ (1п1 !); Затем эту специализацию можно использовать в качестве общей реализации для всех векторов указателей. Префикс 1етр(а1е<> говорит о том, что речь идет о специализации, которая может быть указана без параметра шаблона. Аргументы шаблона, для которых должна использоваться специализация, задаются в угловых скобках <> после имени.
То есть, <ооЫ"> означает, что данное определение должно использоваться как реализация для всех Уес1ог, у которых Тявлястся оо!с(". Уес1ог<поЫ*> является полной специализацией. То есть здесь нет параметра шаблона, который бы следовало задавать или который бы выводился при инстанпированпи, когда мы используем специализацию. Уес1оэ<ооЫ*> используется с векторами, объявленными следующим образом; Уес1ог < ооЫ*> оро, Для определения специализации, которая используется для любого вектора указателей и только для векторов указателей, цам требуется частичная специализация: 1еп!р!а1е<с!азз Т с!азз Уесгог<Т'>.рггиа1е Уес1ог ооЫ'> ( ри(э!(с 1уре<(еТ Уессог<ооЫ*> Вазе; Уесгог() Вазе()() ехр!1с11 Уессог ((п1 1): Вазе (!) ( ) Т'8, е(ет (!п14 ( ге1игп гет1егрге1 саз1<Т'э > (Вазеэе!ет (1)); ) Т" 8, арег агогД ((пг 1) ( ге!игп ге!п1егрге1 сап!< Т'й> (Вазе<прею а1ог[) (!); ) Образец специализации <Т*> после имени означает, что эта специализация должна использоваться для каждого типа указателя, то есть, это определение используется для каждого Уес1ог, аргумент шаблона которого можно выразить в виде Т'.
Например: Уес1ог<Яйаре*> орз; 1'1' <Т*> — это <Влире*>, поэтоли(1 Т вЂ” 56аре Уес1ог<т1-> иррс уу <Т'> — это <!и!**>, лоэгпояу Т вЂ” !п1* Обратите внимание, что когда используется частичная специализация, параметр шаблона выводится по образцу специализации; параметр шаблона не является фактическим аргументом шаблона. В частности, для Уес1ог<ВЬаре*>, Т вЂ” это 5йаре, а не Вйаре*.