Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 51
Текст из файла (страница 51)
Кроме того, поскольку (в отличие от Бшай1а!к) интерфейсы были ограниченными, встроенные типы должны были быть "обернутыми" в большие полиморфные классы (что и обеспечивала г(1НСЬ), а это, в свою очередь, увеличивало потребность в памяти. Даже в нынешнюю эпоху шаблонов во многих проектах все еще делается неоппячааьный выбор при использовании полиморфизмж Очевидно, что существует множество ситуаций когда следует отдать предпочтение динамическому полиморфизму (в качестве яркого приме- 14.6.
Заключение 271 ра можно привести гетерогенные коллекции). Орнако пичул не меньше задач программирования естественно и эффекпавно решаются с использованием шаблонов. Использование статического полиморфнзма хорошо подходит для кодирования наиболее футшаментальных вычислительных структур; необходимосп же выбора общего базового типа приводит к тому, что динамическая полиморфная библиотека обычно хорошо удовлетворяет требованиям конкретной предметной области.
Поэтому не должен вызывать никакого удивления тот факт, что ЗТ1:часть стандартной библиотеки С++ никогда не включала в свой состав полнморфные контейнеры, но зато содержит богатый набор контейнеров и итераторов, которые используют статический полиморфизм. Средние и большие программы, написанные на С++, обычно работают с обоими видами полиморфизма. Порой даже может возникнуть необходимость их весьма тесной комбинации. Во многих случаях выбор оптимального варианта проектирования в свете нашего обсуждения изначально представляется совершенно ясным, однако спустя некоторое время приходит понимание того, что здесь, как нигде, важна роль долгосрочного планирования с учетом всех возможных путей эволюции разрабатываемого проекта.
Глава 15 Классы свойств и стратегий Шаблоны дают возможность параметризовать классы н функции для различных типов. Кажется весьма заманчивым вводить столько параметров шаблонов, сколько нужно для того, чтобы настроить каждый аспект поведения типа или алгоритма. Таким образом, наши "шаблонизированные" компоненты могли бы быть реализованы так, чтобы удовяетворять любым потребностям пользовательского кода. Однако на практике нежелательно вводить большое количество параметров шаблонов для их максимально возможной параметризации.
Необходимость указания всех соответствуюших аргументов в пользовательском коде чрезмерно угомительна. К счастью. большинству дополнительных параметров можно назначить приемлемые значения по умолчанию. В ряде случаев дополнительные параметры полностью определякггся несколькими основньини параметрами и поэтому могут быть вообще опущены.
Для других параметров могут быть заданы значения по умолчанию, зависящие от основных параметров, которые, тем не менее, в ряде случаев все же должны заменяться реальными значениями. Некоторые параметры оказываются не зависящими от основных параметров и в этом смысле сами являются основнымн параметрами. Классы стратегий и классы свонств (или шаблоны кеассое свойств) являются теми компонентами программирования на языке С++, которые значительно облегчают управление множеством дополнительных параметров, появляющихся при разработке мошных шаблонов.
В этой главе приведен ряд ситуаций, в которых они доказывают свою несомненную эффективность, а также демонстрируются различные методы разработки мощных и надежных компонентов для ваших собственных программ. 15.1. Пример: суммирование последовательности Вычисление суммы последовательности значений — довольно тривиальная вычислительная задача. Однако эта простая на вид проблема может служить прекрасным примеРом использования классов стратегий и классов свойств на разных уровнях.
' 274 Глава 15. Классы свойств и стратегий 15.1.1. Фиксированные классы свойств Предположим для начала, что значения, сумму которых необходимо вычислить, хранятся в массиве, и нам заданы указатели на первый суммируемый элемент и на элемент, следующий за последним. Естественно, потребуется написать шаблон, который будет применим для различных типов. Приведенный ниже код может показаться вам очень простым .
// Сгайсв/ассцш1.Ьрр Ибпс)еТ АССПН НРР йя)ееепе АССПН НРР Сешр1аее <Сурелаше Т> 1п11пе Т ассцш(Т сопит* Ьед, Т сопле* епс)) Т Соеа1 = Т(); // Предполагаем, что Т()создает // нулевое значение нЬ11е (Ьед 1= епс)) ( соса1 += *Ьедз ++Ьед; гесцгп соса1; йеп )зТ //АССПН НРР Здесь есть только один тонкий момент: как создать нулевое значение корректного типа для начала процесса суммирования. Мы используем здесь выражение Т (), которое должно правильно работать для встроенных числовых типов, таких, как 1пс и Т1оас, т.е. для целых чисел и чисел с плавающей точкой (см. раздел 5.5, стр.
78). Рассмотрим теперь код, в котором используется наша функция ассцш () . // сгатгв/ассиш1.срр й1пс1ия)е Яассиш1.Ьрр" йепс1цс)е «Товсгеат> 1пс шайп() ( // Создание массива из пяти целочисленных значений Тпс пшв(3 = ( 1, 2, 3, 4, 5)з ! В большинстве примеров, предлагаемых в этом разделе, ради простоты используются обычные указатели. Ясно, что в серьезной разработке может оказаться предпочтительным использование итеряторов (слелуя соглашениям стандартной библиотеки С++ Пя)).
Мм рассмотрим этот аспект несколько позже. 275 15.1. Пример: суммирование последовательности // Вывод среднего значения всй::соиг « ак)зе ачегаде ча1ие ой Г)зе 1пседег ча1иев 1в « ассив(йпив[0], йпив[5]) / 5 « // Создание массива символьных значений сваг паве[] = асевр1асеваг йпг 1епдсЬ = вйзеоб(паве) -11 ' // (Попытка) всй:зсоис « вывода среднего значения символов "сне ачегаде ча1ие ой где сЬагасгегв йп паве « а~к йв ассив(йпаве[0], йпаве[1епдг)з]) / 1епдг)з ' 1п'; « « « В первой половине этой программы использован оператор ассив () для суммирования пяти целочисленных значений.
дпг пив[] = ( 1, 2, 3, 4, 5]г ассив(йпив[0], йпив[5]) После этого полученная сумма просто делится на количество значений в массиве, что дает нам целочисленное среднее значение. Вторая половина программы пытается сделать то же самое для всех букв в слове севр1асев (рассматривая символы от а до к как непрерывную последовательность в наборе символов, что справедливо для АЗСП, но не для ЕВС[)[Сз). По-видимому, результат вычисления должен находиться между значением а и значением з. В настоящее время на большинстве платформ эти значения определяются АБСП-кодами: символ а имеет код 97, а символ к — 122. Следовательно, можно предположить, что результат должен находиться гле-то между 97 и 122.
Однако программа выводит следующее сообщение: ГЬе ачегаде ча1ие ой Г)зе йпседег ча1иев 1в 3 С)ЗЕ аЧЕГадЕ Ча1иЕ Ой С)ЗЕ С)такаССЕГВ 1П "СЕВр1аСЕВ< 1В -5 ЕВСР(С (Вхшндед В(лагу-Содед )эес(ни) 1огетсЬаняе Соде) — зто расширенный лаончнолеслтнчный кол обмена информацией, который представляет собой набор символов 1ВМ, широко используемый на больших машинах 1ВМ. Проблема заключается в том, что наш шаблон был инстанцирован для типа с)заг, у которого оказался слишком маленький диапазон двя накопления даже относительно небольших значений. Ясно, что можно было решить эту проблему, введя дополнительный параметр шаблона Аост, описывакнций тип, который используется для переменной соса1 (и соответственно возвращаемый тип).
Однако тем самым мы бы добавили дополнительную работу всем пользователям: они были бы вынуждены указывать этот тип при каждом обращении к шаблону; например, рассмотренный ранее код использовал бы следующий вызов функции: Глава 15. Классы свойств и стратегий 27б ассша<1пг> (апаше [О), апатае [1епдгЬ) ) Это ие столь существенное ограничение, ио и его можно избежать. Альтернативным подходом к применению дополнительного параметра является создание связи между каждым типом Т, для которого вызывается функция ассша( ), и типом, который будет использоваться для хранения накопленного зиачеиия. Эта связь может рассматриваться в качестве характеристики типа Т, и поэтому тип вычисляемой суммы иногда называется свойством ((тай) Т.
Эта связь может быть закодирована в виде специализации шаблоиа. // сга1св/ассшагга1св2.Ьрр Геазр1асе<сурепаше Т> с1авв Ассшаи1аа1опТга1св; севр1аге<> с1авв Ассипш1аг1оптга1св<сЬаг> ( риЫ Ес: гурес)еЕ Епг АссТу ): севр1асе<> с1авв Ассшаи1агйопТгайсв<вЬогг> ( риЫЕс: сурес)еЕ 1пс АссТ; ); сешр1аге<> с1авв Ассшпи1аг1опТга1св<1пс> ( риЫЕс: сурег)еЕ 1опд АссТ; сешр1аге<> с1авв Ассшпи1аг1оптга1гв<ипв1дцеб 1пс> ( риЫ1с: дурее)еЕ ипв1дпес) 1опд Аост> геазр1асе<> с1авв Ассилш1аг1опТга1св<Е1оас> ( риЫ1с: сурес)еЕ с)оиЬ1е АссТ; ): Шаблон Ассилш1аг1спТгайгв называется шаблоном свойств ((га!м гешр)а(е), поскольку ои хранит свойство типа своего параметра.
(Вообще говоря, а шаблоне свойств может быть как несколько свойств, так и несколько параметров.) В данном случае обоб- 15.1. Пример: суммирование последовательности 277 щенного определения шаблона нег, так как нет хорошего способа для выбора подходящего типа накопления в случае неизвестного исходного типа. Однако можно считать, что таким типом может быть сам тип Т. С учетом сказанного можно переписать наш шаблон ас сии ( ), как показано ниже. // сгатсв/ассша2.Ьрр ()1йпс(ей АССБИ НРР ()с)еййпе АССОМ НРР ()1пс1ис(е "ассшаггайсз2.Ьрр" Сетр1асе <Сурепаще Т> 1п11пе сурепке Ассшпи1агйопТгайсв<Т>::АссТ ассцщ(Т сопве* Ьед, Т сопвг* епд) ( // Возвращаемый тип является свойством типа элементов гурией сурепаще Ассилш1асйопТгайсз<Т>::АссТ АссТ; АссТ Сопа1 = АссТ(); // Предполагаем, что АссТ()создает // нулевое значение ыЬ11е(Ьед 1= епд) ( гога1 += *Ьед; ++Ьед; геенн Госа1; ) ()епдйй // АССОМ НРР Теперь вывод нашей программы выглядит следующим образом: сЬе ачегаде ча1ие об сЬе Тпгедег ча1иев 1в 3 сЬе ачегаде ча1це об гЬе сЬагассегз Тп "гещр1асез" хз 108 В целом внесенные изменения не очень впечатляющи, хотя добавлен очень полезный механизм настройки нашего алгоритма Кроме того, если появятся новые типы, предназначенные для использования с ассша(), соответствующий тип Асст может быть связан с ними посредством простого объявления дополнительной явной специализации класса Ассцлы1аГ1опТх атсз.