108
В самой идее внедрения «не объектно-ориентированного» кода в объекты С++ нет ничего нового или
оригинального. Нас в первую очередь интересует уровень инкапсуляции. Что должен знать клиент о
реальной ситуации? Умные указатели на основе операторов
->
подходят плохо. Клиент должен знать
интерфейс указываемого объекта; следовательно, он должен знать, существует ли указываемый объект,
как устроен его интерфейс и т. д. Интерфейсные указатели, в том числе грани — более удачный
вариант. Если ваша программа написана с применением интерфейсных указателей, вам будет намного
проще вставить новый код, в котором некоторые из этих указателей реализуются в виде посредников.
Проще, хотя и не совсем незаметно для клиента — пока. Помните
базовую форму интерфейсного
указателя с обязательным предварительным объявлением?
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
callback(@fn);
end;
В качестве аргумента процедура
саllbackfn
получает адрес другой процедуры. В данном примере ей
передается
адрес
fn
. При вызове
fn
из
callbackfn
первая
имеет доступ к переменным,
находившимся в стеке в момент получения адреса. В нашем примере
fn
знает значение переменной
n
на момент вызова
саllbackfn
.
Замыкания чрезвычайно полезны для обработки обратных вызовов (callback), поскольку функция
обратного вызова кое-что знает о том, почему она была вызвана. В С
вложенных функций не
существует, а следовательно, замыкания невозможны — их место занимают функторы.