Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 86
Текст из файла (страница 86)
Аналогично с помощью конструктора можно осуществить и передачу связываемого значения (в этом случае в шаблоне следует выделить память для хранения этого значения). С помощью приведенных ниже вспомогательных шаблонов такое выделение памяти можно выполнить как в<~время компиляции, так и во время работы программы. // йипссогв/Ьоипс)ча1.Ьрр ()йпс1ис)е "Суреор.Ьрр" Сешр1аге <Сурепаше Т> с1авв Воипс)ча1 ( ргйчасе: Т ча1ие; риЬ1йс: Сурес)ей т Ча1иет; Воипс)ча1(т ч) : ча1ие(ч) ( Сурепаше ТуреОр<т>::КейТ дее() ( гесигп ча1ие; ) Сешр1асе <Сурепаше Т, Т Ча1> с1авв ЯгаейсВоипс)Ча1 ( риЬ1йс: Сурес)ей т Ча1иет; Т дег() ( гегигп Ча1; ) 1(ак и ранее, здесь мы полагаемся на оптимизацию пустого базового класса (см.
раздел 16.2, стр. 315), которая помогает избежать ненужных накладных расходов в случае. когда представление функтора или связываемой величины не хранит состояния. Таким образом, на начальной стадии разработки шаблон Вйпс)ег выглядит, как показано ниже. 22.8. Связывание значений 479 // йипсгогв/Ыпйег1.Ьрр гешр1аее <Сурепаше РО, Тпг Р, гурепаше Ч> с1авв Вгпйег : ргйчаее РО, рггчасе Ч ( риЬ11с: // Конструкторы: Вйпйег(РОй й): РО(Р) () Вйпйех(РОа й, Ча ч): РО(Е), Ч(ч) () Вйпйег(РОй Й, Ч сопвей ч): РО(й), Ч(ч) () Вйпйег(РО сопвга Е): РО(Е) () В1пйег(РО сопзса й, Ча ч): РО(б), Ч(ч) () В1пйег(РО сопвс* б, Ч сопзсй ч): РО(б), Ч(ч) () сешр1асе<с1авв Т> В1пйег(РОа й, Та ч): РО(й), Ч(ВоипйЧа1<Т>(ч)) () Гетр1аге<с1авв Т> Вйпйег(РОй б, Т совись ч): РО(й), Ч(Воипйча1<Т сопит>(ч)) () Заметим, что кроме конструкторов, в которые передаются экземпляры вспомогательных шаблонов, определяются шаблоны конструкторов, которые автоматически создают оболочку вокруг связываемого значения в виде объекта Воипйча1.
22.8.2. Сигнатуря связывания В шаблоне Вйпйег типы Рагаш)тГ определяются сложнее, чем в шаблоне Оошровег. Невозможно просто воспроизвести типы функтора, на основе которого производится связывание. Поскольку связываемый параметр больше не является параметром нового функтора, соответствующий тип Рагашд)Г следует опустить, а последующие типы сдвинуть на одну позицию. Придерживаясь принципа модульности, введем отдельный шаблон, в котором выполняется операция выборочного сдвига. // Еипсеогв/Ьйпйеграгатз.Ьрр 4йпс1ийе "1 ЕгЬепе1не. Ьрр" сешр1аее<еурепаше Р, Тпс Р> с1авв В1пйеграгашв ( риЬ11с: // На один параметр меньше из-за связывания: епша ( Ицшрагатв,= Р::Ышпрагапш-1 )~ Ме11пе Оошровенагашт(ы) сурейей гуревиче гтТЬепн1ве<(И<Р), Рипсеограгаш<Р, Ы>, Рипссогнагаш<Р, и+1»::Неви1сТ::Туре РагашМИ)) аТ Глава 22. Объекты-функции и обратные вызовы 480 СоврозерагавТ(1) СоврозерагавТ(2) СоврозерагавТ(3) №ипде№ СоврозерагавТ ); В шаблоне Вепс)ег приведенный шаблон используется, как показано ниже.
// Типсгогз/Ьгпс)ег2.)зрр Гевр1асе <Сурепаве РО, гпг Р, Гурепаве Ч> с1азз Вгпс)ег: рггчаге РО, рггчапе Ч ( риЫ1с: // На один параметр меньше из-за связывания: епшп ( ЫшпРагавз = РО::БивРагавз-1 // Возвращаемый тип: сурес)ей Сурепаве РО::КегигпТ КесигпТ; // Типы параметров: сурес)е№ Вгпс)егРагавз<РО, Р> Рагавз; №с)е№1пе СоврозерагавТ(й)) Суреде№ Сурепаве Рогыагс)рагавт<гурепаве Рагавз::Рагав№№Ы№№Т>::Туре Рагав№№Ы№№Т СоврозерагавТ(1); СоврозеРагавТ(2); СоврозерагавТ(3); №ипс)е№ СоврозерагавТ Как обычно, с помощью вспомогательного шаблона Рогыагс)рагавТ удалось избежать излишнего копирования аргументов.
22.8.3. Выбор аргументов Чтобы завершить разработку шаблона Вгпс)ег, осталось решить проблему реализации оператора вызова функции. Как и в шаблоне Согпрозег, мы собираемся перегрузить зтот оператор для вылова фуакгоров с различным количеством параметров. Однако на этот раз стояшля перед нами задача намного сложнее, чем для шаблона Соврозег, поскольку аргу мент, который передается в связываемый функтор, может быть одного из трех видов: ° соответствующий параметр связываемого функтора; ° связываемое значение; ° параметр связываемого функтора, который находится на одну позицию левее от передаваемого аргумента. 22.8. Связывание значений 481 Выбор одного нз трех перечисленных видов будет зависеть от значения Р и от позиции выбираемого аргумента.
Идея, которая поможет достичь желаемого результата, состоит в том, чтобы разработать закрытую встраиваемую функцию-член, которая принимает три возможных значения, передаваемых по ссылке, и возвращает (также по ссылке) одно значение, которое соответствует позиции аргумента. Поскольку зта функция-член зависит от того, какой аргумент выбирается, она будет организована как статический член вложенного шаблона класса.
Этот подход позволяет записать оператор вызова функции следующим образом (ниже приведен случай функтора с четырьмя параметрами; другие подобные операторы реализуются аналогично). // Еипссогв/Ьйпс)егЗ.Ьрр Гешр1асе <Сурепаше РО, 1пс Р, Гурепаше Ч> с1авв В1пс)ег : ргйчасе РО, рг1часе Ч ( риЬ11с: КегпгпТ орегагог()(Рагаш1Т ч1, Рагат2Т ч2, РагашЗТ чЗ) гесигп РО::орегасог()( Агдяе1есг<1>:гбгош(ч1,ч1,Ч::дев()), АгдЯе1есс<2>::Его(ч1,ч2,Ч::дес(]), АгдЯе1есс<3>::йгош(ч2,чЗ,Ч::дев()), Агдяе1есс<4>::Егош(чЗ,чЗ,Ч::дев())); )з Заметим, что в качесше первого и последнего аргумента могут выступать только два значения: сам первый или последний параметр оператора либо связываемое значение. Если А— положение аргумента при вызове соответствующего функтора (в данном примере оно может меняться от 1 до 3), то возможен один из перечисленных ниже вариантов. Если А-Р<0, то выбирается соответствующий параметр, если А-Р=Π— связываемое значение, а если А-Р>0 — параметр, который находится на одну познцшо левее.
Это наблюдение подтверждает правильность определения вспомогательного шаблона, в котором выбирается один вз трех типов на основе знака не являющегося типом аргумента шаблона // бипссогв/вйдпве1есГ.Ьрр ()1пс1ибе "1хсЬепе1ве.Ьрр" сешр1аге <1пс Я, куренное )ЧедТ, Сурепке ЕегоТ, сурепагае РовТ> вегасе Я1дпЯе1есСТ ( гуре~)ей сурепке 1йТЬепн1ве<(Я<0), НедТ, Еурепаше 18ТЬепн1ве<(я>0), Глава22.
Объекты-функции и обратные вызовы 482 РОБТ, Еегот >:: Кеви1ГТ >::Кеви1СТ Кеви1сТ; На данном зтапе все готово для определения шаблона класса АгдЯе1есе. // Еипсгогв/Ьфпс)ег4.Ьрр Сетр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СТ::Туре>::КейТ 22.8.
Связывание значений 483 всагйс Я)сйрт ве1есс (Я)сйрт ргеч агд, ИоЯ)сйрт агд, Вйпс)т Ьоцпс) ча1) ( гегцгп ргеч агд ) ): с1авв В1пс) ( рцЬ11с: всасйс Вйпс)т ве1есс (Я)сйрт ргеч агд, ыоЯ)сйрт агд, В1пс)т Ьоцпй ча1) ( гегцгп Ьоцпд ча1; // Функция выбора: сурет)ей куренное Я1дпЯе1есгт<А-Р, ЫоЯ)с1рт, Вйпс)т, Я)<1рт>::Кеви1ГТ Кесцгпт; Гурес)ей Гурепаве ЯйдпЯе1есГТ<А-Р, ЫФЯ)сйр, Вйпс), Я)сйр>:: Кевц1ГТ Яе1есседт; всасйс Кесцгпт Югом (Я)с1рт ргеч агд, ЫоЯ)сйрт агд, Вйпс)т Ьоцпс) ча1) ( гегцгп Яе1ессес(т:: ве1есг (ргеч агд, агд, Ьоцпй ча1); Приведенный код является, по-видимому, самым сложным в книге. Функция-член Его( ) вызывается в операторах вызова фуиктора.
Частично сложность возникает из-за необходимости выбора нужных типов параметров, из которых выбирается аргумент: типы Я)<1рТ и ЫоЯ)сйрт также удовлетворяют соглашениям, которым должен подчиняться первый и последний аргументы (т.е. повторяют аргументы ч1 и чЗ проиллюстрирован- ного выше оператора). Для определения зтих типов можно применить конструкцию ТуреОр<>:: Кейт. Можно было бы просто создать ссылку на тип с помощью символа й, однако большинство компиляторов еще не умеют обрабатывать "ссылки на ссылки".
Функции, осуществляющие выбор, сами по себе довольно тривиальны, однако они инкапсулированы в типы-члены ь(оЯ)сйр, Я)сйр и В1пс), что облегчает статическую диспетчеризацию соответствующей функции. Поскольку эти функции сами по себе являются просто встраиваемыми передающими вызов функциями, компилятор с хорошей оптимизацией лолжен быть в состоянии "просмотреть" их все и сгенерировать код, близкий к оптимальному. На практике оказалось, что только самые лучшие оптимизаторы из тех, что были доступны во время написания данной книги, генерировали код с хорошей про- 484 изводительностью. Однако большинство других компиляторов тоже выполняли неплохую оптимизацию программ, используюших класс Вфпйег.
Ниже приведена полная реализация шаблона Вхпйег. // Типссогв/Ьфпйег5.)зрр ()1пс1ийе () Тпс1ийе () 1пс1ийе () 1пс1ийе ()1пс1ийе ()Тпс1ийе Гетр1аее <Гурепате РО, 1пг Р, Сурепате Ч> с1авв Вхпйег : рг1чаге РО, рг1чаее Ч ( риЬ11с: // На один параметр меньше из-за связывания: епшп ( ЫитРагзрвв = РО::ИитРагатв-1 )1 // Возвращаемый тип: Гурейеб Сурепате РО::КесигиТ КесигпТ/ // Типы параметров: Сурейей ВфпйегРагатв<РО, Р> Рагатв; ()йеййпе СотроверагатТ(И), Еурейей Сурепате РогыагйРагатТ<гурепате Рагатв::РагапМ()1)()()Т>::Туре Рагат()()И()()Т СотроверагатТ(1); СотроверагатТ(2); СотроверагатТ(3); ()ипйей СотроверагатТ // Конструкторы: Вфпйег(РОа Й): РО(Е) () Вфпйег(РОа Е, Ча ч): РО(й), Ч(ч) () Вйпйег(РОа Й, Ч сопвсй ч): РО(г), Ч(ч) () В1пйег(РО сопзсй Т): РО(б) () ВЗпйег(РО сопвса б, Ча ч): РО(Е), Ч(ч) () Вфпйег(РО сопвса Т, Ч сопвса ч): РО(Е), Ч(ч) () сетр1аге<с1авв Т> Вфпйег(РОа б, Та ч): РО(б), Ч(Воипйча1<Т>(ч)) () Гетр1аге<с1авв Т> Вфпйег(РОй Т, Т сопвга ч): РО(й), Ч(ВоипйЧа1<Т сопвс>(ч)) () // "Вызовы функций": КееигпТ орегасог() () "18СЬепе1ве.ЬРР" "Ьоипйча1.ЬРР" "Тогмагйрагат.ЬРР" "йипссограгат.ЬРР" "Ьфпйеграгатв.ЬРР" "в1дпве1есс.
Ьрр" Глава 22. Объекты-функции и обратные вызовы 22.8. Связывание значений 485 тегпгп РО::орегасог() (Ч::дет() ) ) КегнгпТ орегатог() (Рагат1Т ч1) ( тетпгп РО::оретасог()( Агдяе1ест<1>::актов(ч1,ч1,Ч::дет()), АгдБе1ест<2>::%тот(ч1,ч1,Ч::дет())) ) КетигпТ оретатог() (Рагат1Т ч1, Рагав2Т ч2) ге?птп РО::орегатог()( АгдЯ е1ес т< 1>:: Ттот (ч1, ч1, Ч:: де т ( ) ), АгдЯе1есг<2>::Тгов(ч1,ч2,Ч::дес() ), АтдБе1ест<3>::Тгов(ч2,ч2,Ч::дет())); ) КетпгпТ орегатог() (Рагав1Т ч1, Рагав2Т ч2, РагавЗТ чЗ) ( гетпгп РО::оретатог()( АгдЯе?ест<1>::Тгот(ч1,ч1,Ч::дет()), АтдБе1есс<2>::этот(ч1,ч2,Ч::дег()), АтдЯе1ест<3>::ртов(ч2,чЗ,Ч::дег()), Агдяе1ест<4>::этот(ЧЗ,ЧЗ,Ч::дес())); рг1чате: Гетр1ате<1пт А> с1авв АгдЯе1ест ( рпЬ11с: // Тип до связываемого аргумента турелей турепаве ТуреОР< турепаве 1ЙТ)зепЕ1ве<(А<=Рагавв::?ЧнвРагавв), Рцпстогратав<Рагавв, А>, Рипстотрагав<рагавв, А-1> >::Кеви1ГТ::Туре>::КеТТ ИоЯЫрТ; // Тип после связываемого аргумента турелей турепате ТуреОр< турепаве 1ТТЬепЕ1ве<(А>1), РипстогРагат<Рататв, А-1>, Рппстограгат<Рагавв, А> >::Кевп?тТ::Туре>::КеТТ Я)сТРТ? // Тип связываемого аргумента: Гурес?ей Гурепаве Туребр<турепаве Ч::Ча1пеТ>::Кем ВТпйТ; // Три варианта выбора, // реализованные в разных классах: Глава 22.
Объекты-функции и обратные вызовы 48б с1авв ЫоЯ)стр ( риЫ1с: всастс (чоЯ)сйрт ве1есс (Я)стрт ргеч агд, ИФЯ)стрт агд, ВтпйТ Ьоипй ча1) ( гесигп агд; ) )з с1авв ЯКТР ( риЫ1с: всаг1с Я)стрт ве1есс (ЯК1рТ ргеч агд, МФЯ)стрт агд, ВйпйТ Ьоипй ча1) ( гесигп ргеч агд; ); с1авв Втпй риЫ1с: в Га сто 'Втпйт ве1ес Г ( ЯКТрТ ргеч агд, МоЯ)стрт агд, ВтпйТ Ьоипй ча1) гегигп Ьоипй ча1; l/ Функция выбора: Сурейей Гурепаше ЯтдпЯе1есГТ<А-Р, ЫоЯ)стрт, Втпйт, Я)с1рт>::Кеви1ГТ Кесигпт; Гурейеб сурепшае Я1дпЯе1есГТ<А-Р, МоЯ)с1р, Втпй, ЯК1р>::Кеви1ГТ Яе1ессейт; вгастс Кесигпт йгов (Я)с1рт ргеч агд, НФЯ)стрт агд, Втпйт Ьоипй ча1) ( гесигп яе1ессейт:: ве1есс [ргеч агд, агд, Ьоипй ча1); 22.8.4.