А. Александреску - Современное проектирование на C++ (1119444), страница 72
Текст из файла (страница 72)
Преобразование "указатель на функцию — указатель на функцию" не подходит, поскольку после возврашения из функции гппоиЬ)еп1эратсйег::дбг! информация о статическом типе теряется. и к какому типу следует приводить параметр, неизвестно. (Попробуйте разобрать какой-нибудь код, и вы сразу поймете, в чем дело.) Мы решим задачу динамического приведения типов с помошью простого обратного вызова функций (а не функторов). Таким образом, шаблонный аргумент са11- Ьас!гтеар1ате является указателем на функцию. Решить задачу нам поможет траиплииная фуикция (ггатройпе Гцпсг!оп), также известная под названием саик (гйцп1).
Трамплинные функции — это маленькие функции, выполняюшие небольшую настройку перед вызовами других функций. Обычно они используются разработчиками компиляторов языка С++ для реализации ковариантных типов возврашаемых значений и настройки указателей при множественном наследовании. Мы будем использовать трамплинную функцию, выполняюшую соответствуюшее приведение типов, а затем вызывать функцию с подходяшей сигнатурой, облегчая таким образом работу пользователей.
Однако сушествует одна проблема: объект са11- Ьас!гмар теперь должен хранить два указателя на функции: один предоставляется пользователем, а другой является указателем на трамплинную функцию. Это снижает быстродействие программы. Вместо одного косвенного вызова через указатель у нас есть два. Кроме того, реализация усложняется. Для преодоления этого препятствия воспользуемся следуюшим фактом. Шаблон может получать указатель на функцию в качестве параметра, лишенного типа. (В большинстве книг шаблонные параметры, лишенные типа, представляют собой целочисленные значения.) Вообше говоря, в качестве параметров, лишенных типа, шаблону можно передавать указатели на любые глобальные объекты, в том числе и функции.
При этом должно выполняться единственное условие: функция, адрес которой является шаблонным аргументом, должна иметь внешнюю связь (ехгегпа! (!п)гаке). Статические функции можно легко превратить в функции с внешней связью, удалив ключевое слово зтат1с и поместив их в безымянное пространство имен. Например, в стандартном пространстве имен языка С+ч- можно объявить следуюшую функцию. зтат1с чо1д гопО; х Я убежден, что решение этой проблемы сушествует.
Однако, увы, срок работы иад книгой ограничен контрактом. 297 Глава 11. Мультиметоды В безымянном пространстве имен эта функция объявляется иначе. павезрасе ( чо)д ьопО; Используя указатель на функцию в качестве шаблонного параметра, лишенного типа, мы отказываемся хранить его в ассоциативном массиве. Этот важный момент следует хорошо уяснить. Мы поступаем так потому, что компилятор обладает статической информацией об этом указателе.
Следовательно, компилятор может зафиксировать адрес функции в трамплинном коде. Эту идею можно реализовать в новом классе, используюшем класс вазлсп1зратйсег в качестве внугреннего буфера (ЬасЕ епд). Новый класс гпобзратсйег предназначен для диспетчеризации только функций, а не функторов. Он включает класс ваззсоззратсйег в свой закрытый' раздел и предоставляет соответствуюшие интерфейсные функции. Шаблонная функция глоб зратсйег::АгЫ имеет три шаблонных параметра. Два из них представляют собой левый и правый типы, для которых регистрируется диспетчеризация (классы сопсгетеьйз и сопсгетеяйз). Третий шаблонный параметр (са11Ьасй) является указателем на функцию. Дополнительная функция гпо1 зратсйег::АгЫ перегружает определенную выше шаблонную функцию АгЫ, имеюшую только два параметра.
теюр1ате <с1аээ вазеьйэ, с1азз вазеяйэ = вазеьйэ. яези1ттуре = чо1д> с1аэв гпо)зратсйег вазео)зратсйег<вазеьйз, вазеяйз, яези1ттуре> Ьасйяпд риЬ)зс: теар1ате<с1аээ сопсгетеьйэ, с1азз сопсгетеяйэ, яезо1ттуре (*са11Ьаск)(сопсгетеьйзб, сопсгегеяйзб)> чобд АдоС) ( зтгисг ьоса1 О см. главу 2 ( зтат)с аеэц1ттуре тгарво11пе(вазеьйэб 1Ьз, вазеайзй гйа) ( гетцгп са11Ьаск( дупав)с сайт<сопсгетеьйзй>(1йз), бував)с сазт<сопсгетеайзй>(гйз)); гетцго Ьаскдпд .Адд<сопсгетеьйз, сопсгетеяйз>( йьоса1::тгавро1)пе); Используя локальную структуру,мы определяем трамплинную функцию непосредственно внутри функции АгЫ.
Трамплинная функция осуществляет приведение аргументов к соответствующим типам, а затем передает управление функции, на которую ссылается указатель обратного вызова. Функция АгЫ добавляет трамплинную функцию в объект са11Ьаскмар с помощью функции АсЫ объекта Ьаскдпд (определенной в классе вазепбзратсйег). С точки зрения быстродействия трамплинная функция не создает дополнительных проблем.
Хотя она выглядит как косвенный вызов, это не так. Как указывалось выше, компилятор фиксирует адрес, храняшийся в указателе обратного вызова, в коде функции тгавро1)пе, поэтому второй косвенный вызов не возникает. Более изошренные Часть й. Компоненты 298 компиляторы при малейшей возможности делают трамплинную функцию подставляемой. Использовать новую функцию Ацц легко, суредеУ япп1зрасснег<внаре> п1зрасснег; О возможно в безымянном пространстве имен чо16 насснцессапд1еяо1у(аессапй1ей 1Ьз, по!уй гнз) п1зрасснег 01зр; Нзр.лйб<аессапй1е, яо1у, нассн>0; Благодаря функции-члену лдд класс япп1зрасснег легко использовать.
Он также позволяет при необходимости обращаться к функции лдд, определенной в классе вазеотзрасснег.' 11.7. Класс р п01ара1сйег и симметрия Благодаря динамизму класса гпп1зрасснег добавить в него поддержку симметрии намного проще, чем в статический класс всас1сп1зрасснег. Для этого достаточно зарегистрировать две трамплинные функции: одну — лля вызова исполняющей функции при обычном порядке следования аргументов, а вторую — для перестановки параметров перед вызовом.
Добавим в функцию лоо новый шаблонный параметр. сеир1асе <с1аьл вазеьнз, с1азл вазеяйз = вазелина, сурепаве яезц1стуре = чо1д> с1азз гпп1зрасснег сеир1асе <с1азз сопсгесеьнз, с1авл сопсгесецнз, яезц1стуре (ьса11Ьаск)(сопсгесеснзе, сопсгесеянзе), Ьоо1 зуиаесг1с> Ьоо! БИО с лсгцсс соса! ( ... траыплииная функция остается прежней ... зсас1с чо1б тгааро11пе(вазеянзе гнз, вазесЬзб 1Ьз) ( гесцгп тгааро11пе(1Ьз, гнз); лцц<сопсгесеснз, сопсгесеанз>(еьоса1::тгаиро1тпе); тб (зуваесг1с) лдд<сопсгесецнз, сопсгесеснз>(есоса1::тгаиро11пеа); ) ) ); з Функцию >пп1зрасснег:: лдб нельзя использовать только для регистрации динамически загружаемых функций.
Для того чтобы зто стало возможно, в программу нужно внести небольшие изменения. Глава 11. Мультиметоды Симметричная диспетчеризация в классе гпо1зрассЬег реализована на уровне функций — лля кажюй зарегистрированной функции принимается отдельное решение. 11.8. Двойная диспетчеризация функторов Трюк с трамплинными функциями хорошо работает с нестатическими функциями. Безымянные пространства имен предоставляют ясный способ замены статических функций нестатическими, которые невидимы эа пределами данной единицы компиляции: Однако иногда нужно, чтобы объект обратного вызова (шаблонный параметр Са11Ьасктуре класса ваз(со)зрассЬег) представлял собой нечто большее, чем обычный указатель на функцию. Например, можно потребовать, чтобы каждый обьект обратного вызова сохранял определенное состояние, в то время как функции могут сохранять только значения статических переменных.
Следовательно, возникает необхолимость регистрировать для лвойной диспетчеризации функторы, а не функции. Функторы (глава 5) — это классы, которые перегружают оператор вызова функции О, имитируя синтаксис вызова простой функции. Кроме того, функторы могут использовать переменные-члены для хранения своего состояния и доступа к нему. К сожалению, трюк с трамплинными функциями не применим к функторам именно потому, что функторы сохраняют свое состояние, а простые функции — нет.
(А где трамплинные функции могли бы хранить свое состояние?) Клиентский код может непосредственно использовать класс ваззсо(зрассЬег, конкретизированный соответствующим типом функтора. зсгцсс нассЬгипссог ( чо(б орегасог() (вЬаре8, вЬареб) ( суредеГ ваз(соэ'зрассЬег<вЬаре„ вЬаре, чозд, нассЬьцпссог> нассЬ1пооззрассЬег; Функция нассЬгипссог::орегасогс не является виртуальной, поскольку классу ва- з(со) зрассЬег нужен функтор, имеющий семантику значений, а она плохо согласуется с динамическим полиморфизмом.
Однако функция нассЬгцпссог::орегасогО может пе- реадресовать вызов виртуальной функции. Горазло хуже, что клиент теряет возможности автоматизации, предоставленные диспетчером, — привеление типов и поддержку симметрии. Похоже, что мы слелали шаг назад, но только если вы не читали главу 5 об обобщен- ных функторах. В этой главе определен класс ьцпссог, способный содержать любые функ- торы и указатели на функции, а также объекты самого класса гипссог. Можно даже опре- делять специализированные объекты класса гцпссог, вьводя их из класса гипссогтмр1. В классе вцпссог можно определить приведение типов. Поместив эти средства в библиотеку, можно легко реализовать симметричную диспетчеризацию. Определим класс вцпссого(зрассЬег лля любых объектов класса гипссог.
Этот дис- петчер содержит класс вазэсо( эрассЬег„хранящий внутри себя объекты класса гцпссог. севр1асе <с1азз вазеьЬз, с1аев вазеябв - вазеьЬз, сурепале яезц1стуре = чеза> с1аав гцпссого1эрассЬег ( 300 Часть П. Компоненты суредет гипссог<яези1стуре, тугествт 2(вазеьвзб, вазекбзб)> гипссогтуре; суреде1 ваз1спззрассбег<вазесбз, вазеябз, яези1стуре, гипссогтуре> Васйепдтуре; вас1епдтуре ЬасМепд риЬ11с: Класс гипссогп1зрассбег использует конкретизацию класса ваз1со1зрассбег в качестве выходного буфера.
Класс вазтсо1зрассбег хранит объекты типа гипссогтуре. Они представляют собой объекты класса гипссог, получаюшие два параметра (классов вазесбз н вазеябв) и возврашаюшие объект класса яези1стуре. Функция-член гипссого1зрассвег: гддд определяет специализированный класс ддарсег, производный от класса гипссогтар1. Этот класс предназначен для приведения типов.