А. Александреску - Современное проектирование на C++ (1119444), страница 33
Текст из файла (страница 33)
Для того чтобы удовлетворить новые требования, не нужно писать новый код. Приведенный выше пример компилируется и выполняется без проблем. Почему? Как и прежде, ответ следует искать в определении класса гипссогнапд!ег. Посмотрим, что произойдет после конкретизации шаблона в приведенном выше примере. Функция зсг)пй гипссог<...>::орегасого (1пс 5, 1пс )) 136 Часть й. Компоненты передается виртуальной функции эсг(пд ецпссогнапг)1ег<...>::орегасогО(зпс 1, зпс )) Реализация виртуальной функции выполняет вызов геснгп гцп (1, )); Здесь функция 1цп имеет тип сопэс спаг*(*)(г)оцЫе, г)оцЫе) и вычисляет зиачеиие функции тезсгцпсс(оп.
Когда компилятор обнаружит вызов функции Гцп, ои нормально его скомпилирует, как если бы вы написали этот вызов собственноручно. Слово "нормально" озиачает, что правила приведения типов применяются как обычио. Затем компилятор генерирует код, преобразовывающий переменные ( и ) в тип доцЫе„а результат приводится к типу асс):: зсг)пд. Универсальность и гибкость класса гцпссогиапг)1ег иллюстрирует мощь геиерации кода. Синтаксическая замена аргументов шаблона соответствующими параметрами позволяет оперировать с программой иа уровне исходного текста.
В отличие от этого, мощь объектно-ориентированного программирования проявляется в позднем (после компиляции) связывании имен с их значениями. Таким образом, объектноориентированное программирование способствует повторному использованию кода в форме бинарных компонентов, в то время как обобщенное программирование поощряет повторное использоваиие исходного кода. Поскольку исходный код по определеиию более информативен и находится иа более высоком уровие, чем бинарный, обобшеииое программирование позволяет создавать более сложные конструкции. Олиако это происхолит за счет замедления выполиеиия программы. Со стандартной библиотекой шаблонов невозможно работать так же, как с технологией СОВВА, и иаоборот.
Эти две технологии дополияют друг лруга. Теперь мы можем работать с фуикторами всех видов и обычными функциями, используя один и тот же базовый код. В качестве вознаграждения мы получаем возможность неявно преобразовывать типы аргументов и возвращаемого значения. 5.9. Указатели на функции-члены Указатели на дгункцин-члены ие часто встречаются в программистской практике, ио иногда оии оказываются очень полезными. Оии аналогичны указателям иа обычные функции, ио при вызове фуикции-члеиа нужно передавать объект (помимо аргумеитов).
Синтаксис и семантика указателей иа функции-члены описывается следующим примером. Ф1пс1цг)е <зовсгеав> цМпд паегеэрасе эМ; с1ава яаггос ( рнЫзс: чо(г) катО соус « "иям, иям, иям ...'хп"; чо(д 5реакО ( соцт. « "пиастры! пиастры)~п"; ) ); 137 Глава б. Обобщенные фуикторы зпс еазпО ( // Определяем тип: указатель на функцию-член // класса Раггот, не имеющую параметров и // возвращакхцую значение типа чотб суредег чеза (Раггос::* трмееецп)О; // Создаем объект указанного типа // и инициализируем его адресом функции Раггос::еас треееяцп рдстзчзту = ераггоС::еаС; // Создаем объект класса Раггот Раггос дегопзео; // ...
и указатель на него РаггоС* рбегопзео = Йдегоптео; // с помощью обьекта вызываем функцию-член, // адрес которой хранится в указателе рлссзчт'су. // обратите внииание на оператор .* (дегопзео.«рАссбчзсу) О; // делаем то же самое, но с помощью указателя (рпегопзео->«рлстзч)Су) О; // изменяем действие рлстзчзту = ЬРаггоС::5реах; // проснись, джеронимо! (дегоптео.«рлстзчзту)О; Приглядевшись внимательнее к указателям на функции-члены и двум связанным с ними операторам — .' и ->*, лложно заметить странные особенности. В языке С+а не существует типа для результата работы функций дегоптео.
=рлссз чз ту и дегоп1ео->«рлссбчбсу. С обеими бинарными операциями все в порядке. Они возвращают нечто, к чему можно непосредственно применять оператор вызова функции, но это "нечто" не имеет типа.' Результат выполнения операторов .* и ->«невозможно хранить ни в каком виде, хотя он представляет собой сущность, обеспечивающую связь между объектом и указателем на функцию-член. Эта связь выглядит очень непрочной. Она возникает из небытия сразу после вызова операторов .* или ->*, сушествует довольно долго для того, чтобы к ней можно было применить оператор О и возвращается в обратно небытие. Больше с ней ничего сделать нельзя.
В языке С++, в котором каждый объект имеет тип, результат работы операторов .* или ->«является единственным исключением. Понять сто еще сложнее, чем неоднозначность указателей на функции, вызванную перегрузкой, рассмотренную в предыдущем разделе. Там мы имели слишком много типов, но не могли сделать однозначный выбор; здесь у нас вообще нет ни одного типа.
По этой причине указатели на функции-члены и два связанных с ними оператора представляют собой удивительно непродуманную концепцию в языке С++. Кстати, ссылок на функции-члены не бывает (хотя можно создать ссылки на обычные функции), В некоторых компиляторах языка С++ вводится новый тип, что позволяет хранить результат выполнения оператора . * с помощью следующей синтаксической конструкции. з В описании стандарта языка С++ сказано: "Если результатом выполнения операторов ." н -> яаляетсл функцнл, этот результат можст быть использован лишь а качестве операнда оператора вызова функции О Часть Н. Компоненты 138 // с1ояиге — расширение языка, // определенное в некоторых компиляторах чозд ( с1ояиге:: *дегопзвояногк) 0 = дегопдво.*рАссзчзсу; // Вызываем нужную Функцию дегопзвояиогк0; Тип значения, возвращаемого функцией дегопзвоиог~, не содержит никакой ин- формации о классе Раггос.
это значит, что в дальнейшем функцию дегопзвоиогк можно связать с другим классом. Все, что для этого нужно — тип возвращаемого зна- чения и аргументы указателя на функцию-член. Фактически зто расширение языка представляет собой некую разновидность класса еипссог, но его применение ограни- чено объектами и функциями-членами (на обычные функции и функторы это расши- рение не распространяется).
Перейдем к реализации связывания указателей с функциями-членами класса еипссог. Опыт работы с функторами и функциями подсказывает, что было бы хоро- шо остаться на высоком уровне абстракции и до поры до времени пренебречь специ- фикой. В реализации класса мевеипнапд1ег тип объекта (в предыдущем примере им был класс Раггос) является шаблонным параметром. Более того, указатель на функ- цию-член мы также сделаем шаблонным параметром.
Это позволит свободно выпол- нять автоматическое преобразование типов в реализации класса еипссогнапд1ег, ко- торая приводится ниже. В этой реализации воплощены описанные выше идеи, а так- же некоторые идеи, уже использованные при разработке класса еипссог. севр1асе <с1аяя РагепСЕипстог, Сурепаве Ро(псегтоОЬ)) сурепаве Родпсегтомевеп> с1аяя мевнапд1ег риЬ)зс еипссогтвр1 < сурепаве Рагепсеипссог:гйеяи1стуре, сурепаве Рагепсеипссог::Рагведяс > ( риЬ1(с: суредеЕ сурепаве Рагепсеипссог:гйеяи1стуре кеяи1стуре; мевеипнапд1ег(сопят РодпсегтоОЬ)Ф, Розпсегтомевеп рмевеип) РОЬ) (рОЬ)), рмевеп (рмевеп) () мевеипнапд1ег* с1опе0 сопяс ( гесигп пен мевеипнапд1ег(>сЫя); ) яеяи1стуре орегасог0 () ( гесигп ((+рОЬ) ).*Рмевеп)О; аеяи1стуре орегасог0 (сурепаве Рагептгипетог::Рагв1 р1) ( гесигп ((*рОЬ) ).+рмевеп )(р1); аеяи1стуре орегасог0(сурепаве Рагепсеипссог:: Рагв1 р1, сурепаве Рагепсеипссог::Рагв2 р2) ( гесигп ((*рОЬ) ).*рмевеп )(р1, р2); 139 Глава 5, Обобщенные функторы ргзчате: го(птегтооЬ) роЬ) Ро(птегтомевгп рмевГп Почему параметром шаблонного класса мевгипнапо1ег является тип указателя (го(птегтооЬ)), а не тип самого объекта? Более естественно выглядела бы такая реализация.
гевр1ате <с1аза гагептгипстог, турепаве оЬ)) турепаве го1отегтомевгп> с1азз мевнапо1ег : риЫзс гипстогтвр1 < турепаве гагептгипстог.":яези1ттуре, турепаве гагептгипстог::гагв~з'зт. > ( рг(часе: оЬ)* роЬ) го(птегтомевгп рмевгп риЫ(с: мевгипнапб)ег(оЬ)'* роЬ), го(птегтомевгп рмевгип) : роЬ) (роЬ)), рмевгп (рмевгп) () Понять'такой код было бы проще. Однако первая реализация является более обобщенной.
Во вторую реализацию встроен тип '"указатель на объект", причем этот указатель представляет собой просто адрес объекта класса оЬ) и больше ничего. Может ли это создать проблемы? Конечно, если понадобится применить интеллектуальные указатели на объекты класса мевгипнапд1ег. Убелились? Первая реализация поддерживает интеллектуальные указатели, а вторая — нет.
Первая реализация может хранить любой тип, дейсглаующий как указатель на объект, а вторая хранит и использует только простые указатели. Более того, вторая версия не работает с указателями на константы, Вот как проявляются негативные черты жестко встроенных типов. Попробуем протестировать только что созданную реализацию на примере класса гаггот. Ипс1иде "гипстог.И" В?пс1иде <(озтгеав> из(пд павезрасе зтд; с1азз гаггот риЫзс: чо(г) еатО соит « '"няи, няи, ням ...~п"; чозо зреа<() ( спит. « "пиастры1 пиастры!~п"; )' 140 Часть и.
Компоненты Зпт вазпО ( Раггот йегоп)во; // Определяем два функтора ецпстог<> свд1(фдегопзво, ЙРаггот::еат), свд2(фцегопзво, феаггот: гбреак); // вызываем каждый из них свд10; Д2(); Поскольку класс мевецпнапб)ег должен быть как можно более общим, автоматическое преобразование типов выполняется совершенно свободно — абсолютно так же, как и в классе ецпстогнапб)ег. 5.10. Связывание На этом мы могли бы остановиться. Теперь у нас все есть — класс ецпстог поддерживает все вызываемые сущности языка С++, определенные выше, причем делает это весьма успешно.