|
Программирование >> Синтаксис инициирования исключений
В самой идее внедрения не объектно-ориентированного кода в объекты С++ нет ничего нового или оригинального. Нас в первую очередь интересует уровень инкапсуляции. Что должен знать клиент о реальной ситуации? Умные указатели на основе операторов -> подходят плохо. Клиент должен знать интерфейс указываемого объекта; следовательно, он должен знать, существует ли указываемый объект, как устроен его интерфейс и т. д. Интерфейсные указатели, в том числе грани - более удачный вариант. Если ваша программа написана с применением интерфейсных указателей, вам будет намного проще вставить новый код, в котором некоторые из этих указателей реализуются в виде посредников. Проще, хотя и не совсем незаметно для клиента - пока. Помните базовую форму интерфейсного указателя с обязательным предварительным объявлением? class Pointee; Предварительное объявление class Interface { private: Pointee* pointee; public: Функции класса Проблема кроется в переменной pointee. Клиент должен знать, что указатель ссылается на нечто, даже если он понятия не имеет, на что именно. В части 3 мы попробуем устранить даже это ограничение, а пока будем считать его досадной мелочью. В результате мы приходим к классическому компромиссу: понижение быстродействия интерфейсных указателей (с вынесенными (outline) функциями) за возможность кардинальных изменений реализации без модификации клиентского кода. В большинстве проектов и классов расходы с лихвой компенсируются ускорением цикла разработки. Функторы Напоследок мы познакомимся с одной диковинкой C++, которая называется функтором (functor). Функторы играют для функций ту же роль, что и интерфейсные указатели для объектов. Одна из проблем, вечно мучивших программистов на С - то, что все функции находятся в глобальном пространстве имен, то есть вызванная функция имеет доступ только к данным, хранящимся в ее аргументах, и глобальным переменным. Если передать адрес функции еще кому-то, то при вызове функции по адресу она не будет помнить, как выглядел окружающий мир во время получения ее адреса. В таких языках, как Паскаль, эта проблема изящно решается получением замыкания (closure) на момент получения адреса функции. procedure p(n: integer); var procedure fn; begin do something(n); end; begin ca11back(@fn); end; В качестве аргумента процедура са11backfn получает адрес другой процедуры. В данном примере ей передается адрес fn. При вызове fn из callbackfn первая имеет доступ к переменным, находившимся в стеке в момент получения адреса. В нашем примере fn знает значение переменной n на момент вызова са11backfn. Замыкания чрезвычайно полезны для обработки обратных вызовов (callback), поскольку функция обратного вызова кое-что знает о том, почему она была вызвана. В С вложенных функций не существует, а следовательно, замыкания невозможны - их место занимают функторы. class Fn { private: int number; public: f(int n) : number(n) {} void operator() () { do something(number); } void callbackfn(Fn); void p(int n) ca11backfn(Fn(n)); void ca11backfn(Fn fn) Что-то делаем fn(); Вызвать функцию fn с помощью функции operator() Весь секрет кроется в двух выражениях. Функция ca11backfn(Fn(n)) передает функции анонимный экземпляр класса Fn. Аргумент его конструктора содержит информацию, включаемую в псевдозамыкание , которое поддерживается переменными класса Fn. Выражение fn(); может показаться обычным вызовом функции, но на самом деле в нем вызывается операторная функция operator() класса Fn. В свою очередь, эта функция вызывает глобальную функцию do something с использованием данных замыкания. И кому после этого нужен Паскаль? Операторная функция operator() может вызываться с произвольным набором аргументов. Чтобы добавить новые аргументы, укажите их во вторых скобках в объявлении класса. Также разрешается многократная перегрузка оператора () с разными сигнатурами. Ниже приведен тот же пример, в котором одна из версий операторной функции operator() вызывается с аргументом. class Fn { private: int number; public: f(int n) : number(n) {} void operator() () { do something(number); } void operator() (char* s) do something(number); cout << Что-то делаю с << s << endl; void callbackfn(Fn); void p(int n) ca11backfn(Fn(n)); void ca11backfn(Fn fn) Что-то делаем fn( ca11backfn ); Эта маленькая идиома выглядит довольно изящно, однако того же эффекта можно добиться и без оператора () . class Fn { private: int number; public: f(int n) : number(n) {} void do something() () { ::do something(number); } void do something() (char* s) do something(number); cout << Что-то делаю с << s << endl; void callbackfn(Fn); void p(int n) ca11backfn(Fn(n)); void ca11backfn(Fn fn) Что- то делаем fn.do something( ca1Ibackfn ); Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () - в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором () ; в противном случае пользуйтесь обычными функциями класса.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |