Программирование >>  Синтаксис инициирования исключений 

1 ... 29 30 31 [ 32 ] 33 34 35 ... 82


В самой идее внедрения не объектно-ориентированного кода в объекты С++ нет ничего нового или оригинального. Нас в первую очередь интересует уровень инкапсуляции. Что должен знать клиент о реальной ситуации? Умные указатели на основе операторов -> подходят плохо. Клиент должен знать интерфейс указываемого объекта; следовательно, он должен знать, существует ли указываемый объект, как устроен его интерфейс и т. д. Интерфейсные указатели, в том числе грани - более удачный вариант. Если ваша программа написана с применением интерфейсных указателей, вам будет намного проще вставить новый код, в котором некоторые из этих указателей реализуются в виде посредников. Проще, хотя и не совсем незаметно для клиента - пока. Помните базовую форму интерфейсного указателя с обязательным предварительным объявлением?

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 );

Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () - в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором () ; в противном случае пользуйтесь обычными функциями класса.



1 ... 29 30 31 [ 32 ] 33 34 35 ... 82

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика