Типобезопасные обратные вызовы в C++ - Генерация обратных вызовов и пример использования

ОГЛАВЛЕНИЕ

Генерация обратных вызовов

i_CallbackCalculate и i_CallbackPrint нельзя было использовать до сих пор, потому что они еще не были инициализированы, то есть им не была присвоена никакая функция. Если вызвать i_CallbackCalculate.Valid(), результатом будет false, а значит, нельзя использовать обратный вызов. Если попытаться (i_CallbackCalculate.Execute(...)), программа аварийно завершится посредством утверждения. Чтобы сгенерировать инициализированные обратные вызовы для вышеназванных функций, надо использовать cCallGen. cCallGen (генератор обратного вызова) унаследован от cCall и применяется всего один раз для инициализации. cCallGen определяется как cCallGen <_Class, _ReturnType, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5>.

cCallGen <cMyApplication, int, float, bool, char*> i_CallGenCalculate(this, Calculate);
// и
cCallGen <cMyApplication, void> i_CallGenPrint(this, Print);

Видно, что cCallGen принимает на один аргумент шаблона больше, чем cCall. Первый тип (cMyApplication) – класс, содержащий функцию обратного вызова; остальное точно такое же, как и у cCall. Параметрами, передаваемыми конструктору, являются указатель this класса, содержащего функцию обратного вызова, и сама функция обратного вызова (Calculate, Print). Теперь ясно, зачем нужны два класса обратного вызова. cCallGen может использоваться только внутри класса, содержащего функцию обратного вызова (здесь cMyApplication), потому что ему требуется указатель this. cCall можно создать в любом классе, и позже ему можно присвоить совместимый обратный вызов, переданный откуда угодно:

i_CallbackCalculate = i_CallGenCalculate;
i_CallbackPrint     = i_CallGenPrint;

Такое присваивание возможно, потому что cCallGen унаследован от cCall. Теперь можно выполнить обратный вызов, написав:

int Result = i_CallbackCalculate.Execute(3.448, true, "Hello world");
// и
i_CallbackPrint.Execute();

Если функции обратного вызова статические, используется cCallGenS вместо cCallGen.

Пример использования

Следующий пример демонстрирует класс списка, вызывающий внешние функции для сортировки данных. Присваивая разные функции обратного вызова, можно сортировать список по разным критериям.

Список становится очень гибким, потому что вызывающий оператор может влиять на поведение сортировки, не манипулируя кодом в cList. Это сущность C++. Можно сделать то же самое путем наследования от cList и переписывания функции Compare. Однако обратные вызовы позволяют динамически менять поведение списка при выполнении путем вызова SetCallback(...) всегда, когда нужно.

Обзор интерфейса

cCall    cCallGen (для обратных вызовов членов и виртуальных обратных вызовов)    cCallGenS (для статических обратных вызовов)

Конструктор:

cCall <tRet [,tArg1,,,tArg5]> MyCall()    Конструктор:
cCallGen <cClass, tRet [,tArg1,,,tArg5]> MyCallGen(Instance, Function)    Конструктор:
cCallGenS <tRet [,tArg1,,,tArg5]> MyCallGen(Function)
cCall& operator=(cCall &ExtCall)
cCall& operator=(cCallGen &ExtCallGen)    -    -
bool Valid()    -    -
tRet Execute([tArg1, tArg2, tArg3, tArg4, tArg5])    -    -

Чтобы увидеть, как все это работает вместе, скачайте простой пример кода.

Ошибки

Выдаются ошибки компилятора при попытке присвоить обратный вызов несовместимому обратному вызову (с разными аргументами или типом возвращаемой переменной). Выдаются ошибки времени исполнения (утверждение) при передаче неверного количества аргументов в i_Callback.Execute(...) или при попытке исполнить неинициализированный обратный вызов.

Boost

Preprocessor.h был извлечен из Boost::function. Boost - огромная платформенно-независимая библиотека C++ (35 Мб), использующая массу хитростей, чтобы заставить компилятор делать по идее невозможные вещи. Так как все компиляторы ведут себя по-разному и имеют отличные ограничения, Boost содержит тысячи операторов выбора, директив #define и typedef, чтобы обеспечить свою работу во всех компиляторах. В Boost есть много интересных вещей, но имеются недостатки:

•    Boost огромна (35 Мб).
•    Замедляет процесс компиляции.
•    Иногда вызывает фатальный сбой компьютера, требующий перезагрузки.
•    Почти невозможно понять очень загадочный код в 100 заголовочных файлах, используемых для Boost::function.

Разработанный класс обратного вызова намного удобней, чем Boost::function.

QT

QT – еще одна библиотека, о которой надо знать. QT – огромная платформенно-независимая библиотека графического пользовательского интерфейса(GUI). Однако QT интересна, даже если программировать GUI не нужно, потому что она предлагает платформенно-независимые функции для доступа к файлам, таймеры, и т.д. QT обходит ограничения в компиляторе и даже расширяет сам язык C++ путем встраивания дополнительного препроцессора в компилятор. Это обеспечивает новый функционал, такой как более легкая поддержка нескольких языков, информация о типах в процессе исполнения, защищенные указатели, более разборчивый код и сигналы и слоты, являющиеся улучшенными обратными вызовами (смотрите часть 2 этой серии статей).

Увы, у QT также есть минусы:
•    Она стоит $1500 (за исключением разработки свободного программного обеспечения для Linux).
•    Она огромная (20 Мб исходников).
•    Она замедляет процесс компиляции.
•    Программы, написанные с помощью QT, вынуждены предоставлять несколько дополнительных динамических библиотек с полным размером 6 Мб.
•    Сигналы и слоты нельзя использовать для возврата значения вызывающей функции.
Поэтому, если вас не устраивают большие библиотеки типа Boost или QT, этот крохотный класс обратного вызова от ElmueSoft точно не помешает.

Во второй части мы обсудим типобезопасные сигналы и слоты C++ (события и делегаты). Прочитайте вторую часть, основанную на данной статье и реализующую очень удобную систему сигнал / слот (событие / делегат) на C++, не требуя никакой большой библиотеки!
•    Сигнал (событие), возбужденный где угодно в любом классе кода, может быть получен слотами (делегатами) в любом классе кода.
•    Можно подключить столько сигналов к слоту и столько слотов к сигналу, сколько нужно.
•    Можно передать от 0 до 5 аргументов любого типа сигналу, и можно объединить несколько возвращаемых значений из слотов во всего одно возвращаемое значение, возвращаемое вызывающей функции.
•    Если класс уничтожается, все подключенные сигналы или слоты автоматически отключаются.
•    Сигналы защищены от повторного вхождения.
•    Оптимизирован для высокой скорости.
•    Размер скомпилированного кода менее 1 Кб, не нужны никакие дополнительные библиотеки.
•    Гораздо удобней, чем Boost::signals.
•    Доступны некоторые дополнительные возможности.
•    Платформенно-независимый: работает на Windows, Linux, Mac, и т.д...
•    Был испытан в Visual Studio 6.0, 7.0, 7.1 и 8.0 (= Visual Studio 6 вплоть до .NET 2005)