Анимация
JavaScript
|
Главная Библионтека typedef void (*TpFun)(int, double); Способ 1: инициализация TpFun pF = TestFunction; Functor<void, TYPELiST 2(int, double)> cmdl(4, 4.5); Способ 2: приведение типов Functor<void, int, double)> cmd2( static cast<TpFun>(TestFunction)); все правильно! cmd2C4, 4.5); Оба способа инициализации позволяют компилятору распознать требуемую версию функции TestFuction, которая должна получать параметры типа int и double, а возвращать - значение типа void. 5.8. Преобразование типов аргументов и возвращаемого значения в идеале преобразование типов для функторов должно выполняться так же, как и при вызовах обычных функций. Тогда можно было бы написать примерно такой код. #include <string> #include <iostream> #include "Functor.h" using namespace std; Аргументы игнорируются - в данном примере они не представляют интереса const char* TestFunctionCdouble, double) static const char buffer[] = "Hello, world!"; можно вернуть указатель на статический буфер return buffer; int main С) { Functor<string TYPELIST 2(int, int)> cmdCTestFunction); должен выводить на печать строку "world!" cout « cmdClO, 10).substr(7); Несмотря на то что сигнатура фактической функции TestFunction немного отличается (она получает два параметра типа double и возвращает константу типа char*), ей можно поставить в соответствие функтор Functor<string, TYPELlST 2(int, int)>. Предполагается, что это возможно, поскольку тип int можно неявно преобразовать в тип double, а значение типа const char* - в тип string. Если класс Functor не поддерживает те же преобразования, что и язык С++, то такие жесткие ограничения следует считать неоправданными. Для того чтобы удовлетворить новые требования, не нужно писать новый код. Приведенный выще пример компилируется и выполняется без проблем. Почему? Как и прежде, ответ следует искать в определении класса FunctorHandl ег. Посмотрим, что произойдет после конкретизации щаблона в приведенном выще примере. Функция string Functor<...>::operator()(int i, int j) передается виртуальной функции string FunctorHandler<.. .>:-.operatorOCint i, int j) Реализация виртуальной функции выполняет вызов return fun Ci, j) ; Здесь функция fun имеет тип const char*(*)(double, double) и вычисляет значение функции TestFunction. Когда компилятор обнаружит вызов функции fun , он нормально его скомпилирует, как если бы вы написали этот вызов собственноручно. Слово "нормально" означает, что правила приведения типов применяются как обычно. Затем компилятор генерирует код, преобразовывающий переменные i и j в тип double, а результат приводится к типу std::string. Универсальность и гибкость класса FunctorHandler иллюстрирует мощь генерации кода. Синтаксическая замена аргументов шаблона соответствующими параметрами позволяет оперировать с программой на уровне исходного текста. В отличие от этого, мощь объектно-ориентированного программирования проявляется в позднем (после компиляции) связывании имен с их значениями. Таким образом, объектно-ориентированное программирование способствует повторному использованию кода в форме бинарных компонентов, в то время как обобщенное программирование поощряет повторное использование исходного кода. Поскольку исходный код по определению более информативен и находится на более высоком уровне, чем бинарный, обобщенное программирование позволяет создавать более сложные конструкции. Однако это происходит за счет замедления выполнения программы. Со стандартной библиотекой шаблонов невозможно работать так же, как с технологией CORBA, и наоборот. Эти две технологии дополняют друг друга. Теперь мы можем работать с функторами всех видов и обычными функциями, используя один и тот же базовый код. В качестве вознаграждения мы получаем возможность неявно преобразовывать типы аргументов и возвращаемого значения. 5.9. Указатели на функции-члены Указатели на функции-члены не часто встречаются в профаммистской практике, но иногда они оказываются очень полезными. Они аналогичны указателям на обычные функции, но при вызове функции-члена нужно передавать объект (помимо аргументов). Синтаксис и семантика указателей на функции-члены описывается следующим примером. #include <iostream> using namespace std; class Parrot { public: void Eat() { cout « "Ням, ням, ням ...\n"; void SpeakO { cout « "Пиастры! Пиастры!\n"; }; • int mainO { Определяем тип: указатель на функцию-член класса Parrot, не имеющую параметров и возвращающую значение типа void typedef void (Parrot::* TpMemFun)(); Создаем объект указанного типа и инициализируем его адресом функции Parrot::Eat TpMemFun pActivity = &Parrot::eat; Создаем объект класса Parrot Parrot geronimo; ... и указатель на него Parrot* pGeronimo = &geronimo; С помощью объекта вызываем функцию-член, адрес которой хранится в указателе pActivity. обратите внимание на оператор .* (geronimo.*pActivity)О; Делаем то же самое, но с помощью указателя (pGeronimo->*pActivity)(); изменяем действие pActivity = &Parrot::Speak; проснись, Джеронимо! (geronimo.*pActivity)(); Приглядевшись внимательнее к указателям на функции-члены и двум связанным с ними операторам - .* и ->*, можно заметить странные особенности. В языке С-1-I-не сушествует типа для результата работы функций geronimo.*pActivity и geronimo->*pActivity. С обеими бинарными операциями все в порядке. Они возвращают нечто, к чему можно непосредственно применять оператор вызова функции, но это "нечто" не имеет типа. Результат выполнения операторов .* и ->* невозможно хранить ни в каком виде, хотя он представляет собой сущность, обеспечивающую связь между объектом и указателем на функцию-член. Эта связь выглядит очень непрочной. Она возникает из небытия сразу после вызова операторов .* или ->*, сушествует довольно долго для того, чтобы к ней можно было применить оператор () и возвращается в обратно небытие. Больше с ней ничего сделать нельзя. В языке С-1-1-, в котором каждый объект имеет тип, результат работы операторов . * или ->* является единственным исключением. Понять его еше сложнее, чем неоднозначность указателей на функции, вызванную перефузкой, рассмофенную в предыдущем разделе. Там мы имели слищком много типов, но не могли сделать однозначный выбор; здесь у нас вообще нет ни одного типа. По этой причине указатели на функции-члены и два связанных с ними оператора представляют собой удивительно непродуманную концепцию в языке С++. Кстати, ссылок на функции-члены не бывает (хотя можно создать ссылки на обычные функции). В некоторых компиляторах языка С-1-I- вводится новый тип, что позволяет хранить результат выполнения оператора . * с помощью следующей синтаксической консфукции. В описании стандарта языка С++ сказано: "Если результатом выполнения операторов .* и -> является функция, этот результат .может быть использован лишь в качестве операнда оператора вызова функции ()". 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 [ 40 ] 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |