А. Александреску - Современное проектирование на C++ (1119444), страница 34
Текст из файла (страница 34)
Однако, как мог убедиться Пигмалион, начиная работу, невозможно предсказать, каков будет ее результат. Поскольку класс ецпстог уже готов, возникают новые идеи. Например, хотелось бы иметь возможность конвертировать тил ецпстог в другой тип.
Одним из таких преобразований является связывание (Ь1пд1пя). Пусть задан класс ецпсгог, получающий целые числа. Мы хотим связать одно из этих целых чисел с некоторым фиксированным значением, а второе оставить переменным. Такое связывание порождает новый класс Ецпстог, лолучаюший только одно целое число, поскольку второе зафиксировано и, следовательно, известно.
Описанное выше связывание иллюстрируется следующим примером. чо)б ФО ( // определяем функтор с двумя аргументами Ецпстог<чозд, туРЕС1зт 2(зпт, зпг)> свд1(зоветб)пй); // Связываем первый аргумент со значением 10 еипстог<чозд, Зпт> свд2(взпдез гзт(свб1, 10)); // эквивалентно свд1(10, 20) свд2(20); // Затем связываем первый (и только первый) аргумент // функтора свд2 со значением З0 ецпссог<чо)д> свдЗ(вз пдез гзт(свд2, З0)); // Эквивалентно свд1(10, ЗО) свдЗ О; ) Связывание — мощное оружие.
Оно позволяет хранить не только вызываемые сущности, но и часть (или все) их аргументы. Это значительно расширяет возможности функторов, поскольку теперь можно упаковывать функции и аргументы, не прибегая к генерации дополнительного кода для их связывания. Например, представьте себе реализацию операции повтора (гено) в текстовом редакторе. Когда пользователь набирает букву "а", выполняется функция оосцвепт::1пзегтспаг('а'). добавим готовый функтор, содержащий указатель на класс оосцвепс, функцию-член 1пзегтспаг и фактический символ.
если пользователь 'выполняет в меню пункт Геенно, достаточно лишь активизировать функтор — и все. Более подробно операции отката и повтора обсуждаются в разделе 5.14. 141 Глава б. Обобщенные функторы Связывание является мощным средством еше и по другой причине. Предстаньте, что вы применяете класс Рипстог лля вычислений, а его аргументы — зто окружение, необходимое для их выполнения. До сих пор класс Рипстог задерживал вычисления, сохраняя указатели на функции и указатели на методы. Однако в классе Рипстог хранятся лишь вычисления, но нет информации об их окружении. Связывание позволяет функтору хранить часть окружения вместе с вычислениями и сократить потребность в пересылке переменных окружения во время вызова.
Перед тем как перейти к реализации, подытожим наши требования. В конкретизации класса Рипстог<а, ть1зт> мы хотим связать первый аргумент (ть1зт::недо) с фиксированным значением. Следовательно, типом возвращаемого значения является класс Рипстог<в, ть1ат::таз1>. Реализовать класс вз пдеггз' гас очень легко.
Нужно лишь учесть, что здесь используются две конкретизации класса Рипстог: входная и результируюшая. Входной тип Рипстог передается в качестве параметра Рагептгипстог, а результирующий тип Рипстог вычисляется. теар1ате <с1ааз 1псоюпд> с1авв Втпдегк(гат риЬ)зс Рипстогтар1<турепаве 1псовзпц::аези1ттуре сурепаае 1псоазпц::Агдиаепсз::та11> туредег Рипстог<турепаае 1псов)пд::аези1ттуре, 1псовт'пц::Агдиаептз::та11> оитцозпд; суреоеУ сурепаве тпсою пд::Рага1 воипд; туребег сурепаае тпсоаз пд:: аези1ттуре кеви1стуре; риЬ11с: в1пдегР(гзт(сопзт тпсоа1пдб Тип, воипо Ьоипд) Уип (Рип), Ьоипд (Ьоипд) В1пдегР1 гзт* с1опеО сопзт ( гетигп пеп В)пбегг) гзт(>сЫз); аези1стуре орегатогО О гетигп Рип (Ьоипо ); вези1ттуре орегатогО(сурепаае оисцо1пц::Рагв1 р1) гесигп тип (Ьоипд, р1); вези1ттуре орегатогО(сурепаае оисдо1пд::Рага1 р1, турепаве оитцо)пц::Рага2 р2) ( гесигп Уип (Ьоипд, р1, р2); ) рг1иасе: 1псоюпд Гип Всыпь Ьоипо ); Часть!!.
Компоненты Шаблонный класс взпдегг(гзс работает в сочетании с шаблонной функцией взпдг(гас. Достоинством шаблонной функции аз пдгз гзс являешься то, что она автоматически выводит свои шаблонные параметры из типов получаемых ею фактических параметров. // определение класса вз пдеггз гзстгаз сз // находится в файле гипссог.Ь севр1асе <с1аьа гссог> сурепаве юг)часе::взпдегг)гзстгазсз<гссог>::воипдгипссогтуре Вз пдгз Гас( солзс гссогв Гип, сурепаве гссог::гагв1 Ьоипд) суредет сурепаве ргдчасе::в)пдеггзгзстгазсз<гссог>::воипдгипссогтуре оисдозпд; гесигп оисдозпд(зсд::аисо рсг<сурепаве оисдозпд:: 1вр1>( пев вз'пдеггзгзс<гссог>(Гип, Ьоипд))); Связывание прекрасно сочетается с автоматическим преобразованием типов, налеляя класс гипссог невероятной гибкостью.
Это иллюстрирует следующий пример. сопзс снаг* гип(з'пс з', зпс )) саит « гип(" « 1 « ", " « ) « ") са11еАп"; 1пс вазпО ( гипссог<сопас сьаг*, тчгеьсвт 2(сьаг, дпс)> гигип); гипссог<зсд::зсгзпд, тчгеьсдт 1(доиЬ)е)> Я( В1пдг(гзс(т1, 10)); // выводит на печать строку 'гип(10, 19) са11ед" Г2 (15); 5.11. Сцепление В книге Оашгпа ег а!.
(1995) приведен пример класса масгосоввапд, в котором хранится линейный набор (список или вектор) объектов класса соввапд. При выполнении этого класса последовательно выполняются все хранящиеся в нем команды. Эго свойство мажет оказаться очень полезным. Например, вернемся к примеру, связанному с откатом и повторным выполнением операции ипдо/геди, Каждая операция "до" лолжна сопровожлаться несколькими операциями отката "ипдо". Например, вставка символа может автоматически приводить к прокругке текстового окна (в некоторых текстовых редакторах эта операция применяется для улучшения внешнего вида текста). Отменяя зту операцию, вы возвращаете окно в прежнее положение.
(В большинстве текстовых редакторов эта операция реализована неправильно. Досадно!) Чтобы выполнить обратную прокрутку (ипзсгой), нужно хранить несколько объектов класса соввапд в одном объекте класса гипссог и выполнять их как одно целое. Функция-член оосивепс: ."1паегссйаг заталкивает объект класса масгосоввапд в стек отката. В состав класса масгосоввапд могут входить функции-члены оосивепс:: ое1есесйаг и М пдов:: всго11. Послелнюю функцию можно связать с аргументом, запоминающим старое состояние. (И вновь связывание оказывается очень удобным инструментом.) 143 Глава 5. Обобщенные функторы В библиотеке ЕоЫ определен класс гипссогспаз и и вспомогательная функция сЬазп. теир)ате <с)аьв гип1, с)аьа гип2> гип2 с)зази( сопят гип1б гип1, сопле гип26 гип2); Реализация класса гипстогс)зазп тривиальна — он хранит два функтора, а оператор гипстогсЬазп::орегатогО вызывает их один за другим.
Функция спазп завершает наше описание поддержки макрокоманд. Одно из достоинств такой реализации заключается в том, что список команд неограничен. Ни шаблонная функция взпдг( гзт, ни функция спазп не требуют вносить никаких изменений в класс гипстог. Это свидетельствует о том, что вы сами можете разработать аналогичные функшюнальные возможности.
5.12. Первая практическая проблема: стоимость функций пересылки В принципе разработка шаблонного класса гипстог завершена. Теперь мы сосредоточимся на вопросах его оптимизации, стремясь, чтобы он работал как можно эффективнеер Рассмотрим в классе гипстог одну из перегрузок оператора О, пересылающую вызов интеллектуальному указателю. // внутри класса гипстог<а, тьззт> я орегатогО(гагю1 р1, гагм2 р2) гетигп (*артюр) )(р1, р2); ) При каждом вызове оператора О создается ненужная копия каждого аргумента. Если классы гагм1 и гагм2 достаточно велики, это снизит производительность работы программы. Довольно странно, но даже если оператор О класса гипстог сделать подставляемым (ш!ше), компилятор будет по-прежнему тиражировать лишние копии аргументов.
В задаче 4б книги Саттера (Бицег, 2000) описывается современная модификация ' языка, сделанная непосредственно перед его стандартизацией. Функциям пересылки было запрещено создавать копии (е)101пя гог сору сопзгшстюп). Компилятору позволено создавать копии лишь для возвращаемого значения, поскольку возврат значений нельзя оптимизировать вручную.
Помогут ли ссылки решить эту проблему? Используем следуюший код. // внутри класса гипссог<а, тьззт> я орегатогО(гагм16 р1, гагм2й р2) гетигп (*зртвр) )(р1, р2); ~ Обычно пре:хдевремеииая оптимизация нежелатсльна. Одна из причин заключается в том, что программисты не иог)т правильно определить, какую часть программы следует оптимизировать, а какую — нет (что еше важнее).
Однако разработчики библиотек находятся в другой ситуации. Они не знают, будут или иет использованы их библиотеки в критически важных частях какопэ-то приложения, поэтому стремятся максимально оптимизировать библиотеку. 144 Часть )). Компоненты Все это выглядит прекрасно и может на самом деле работать, пока вы не попробуете выполнить такой код. чоз6 теятяцпст(оп(516::зтгзпйб, )пт); Рцпстог<чо)6, туРет15т 2(516::зтгзпйв, 1пт)> св6(тезтяцпст(оп); 5тг1п9 5; св6(5, 5); // ошибка! Компилятор споткнется на последней строке, выдав сообщение: "Ссылки на ссылки не допускаются!". (На самом деле сообщение может быть более таинственным.) Фактически такая конкретизация может преобразовать класс гагаХ в ссылку на обьект класса 516:: ятг1п9.