Выбрать главу

3.2. Качественный анализ

3.2.1. Матрица соответствия

Матрица соответствия строится в виде таблицы. В строках выписываются требования, в столбцах – способы реализации, в ячейках – признаки, указывающие, насколько реализация поддерживает соответствующий критерий (Табл. 7.)

Табл. 7. Качественный анализ реализаций обратных вызовов

Легенда: ▪ полностью поддерживается; ▫ поддерживается частично; пустое поле – не поддерживается

По каким соображениям мы назначили оценки?

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

Независимость компонентов. Полностью независимыми будет реализация с помощью указателей на функцию: как бы мы не модифицировали код исполнителя, как бы не меняли используемый контекст, код инициатора остается неизменным, даже не требуется его перекомпиляция. Это одна из причин, почему указанная реализация подходит для построения системных API. Лямбда-выражения являются относительно независимыми: при любом изменении состава и типов захваченных переменных код инициатора остается неизменным, но он будет требовать перекомпиляции, поскольку реализован с использованием шаблонов. Указатели на методы классов являются частично независимыми, поскольку требуют предварительного объявления класса в инициаторе. Использование функциональных объектов порождает монолитную архитектуру, где инициатор и исполнитель зависят друг от друга.

Отсутствие трансляции контекста. Указатели на функции и статические методы требуют трансляции контекста, остальные реализации этого не требуют.

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

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

Полиморфизм. Указатель на метод-член класса поддерживает полиморфизм подтипов (наследование и виртуализация), лямбда-выражения поддерживают специализированный полиморфизм (различный код в зависимости от состава и типов захваченных переменных). Остальные реализации полиморфизм не поддерживают.

Быстродействие. Самым быстродействующим является функциональный объект, практически не отстает от него и лямбда-выражение. Это связано с тем, что их код может встраиваться в точку вызова. Несколько медленнее работают указатели на функцию и на статический метод, поскольку их код выполняется через вызов функции13. Медленнее всего работает указатель на метод-член класса, поскольку ему необходимо обращение к таблице виртуальных функций.

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

C++ API. Лямбда-выражения не подходят для использования в C++ API: хотя инициатор не требует изменений при модификации исполнителя, но ему требуется перекомпиляция. Не подходят для С++ API также функциональные объекты, поскольку изменение функционального объекта затрагивает как инициатор, так и исполнитель.

3.2.2. Выбор реализации

Итак, мы построили матрицу соответствия, проанализировали, насколько реализации соответствуют выбранным критериям. Что же выбрать для конкретного случая? Для решения этого вопроса необходимо определить, какой критерий сейчас является наиболее важным, и выбрать реализацию по этому критерию. Так, например, если самым важным является возможность проектирования системного API, то следует выбрать указатели на функцию. Если самым важным является быстродействие, то следует выбрать функциональные объекты. Если самым важным является гибкость, то следует выбрать указатели на член класса.

вернуться

13

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