Обратные вызовы в C++ - страница 15
2.5.3. Исполнитель
Исполнитель реализовывается в виде лямбда-выражения, а передача его как аргумента инициатору зависит от способа реализации последнего. Если исполнитель реализован в виде шаблона класса (п. 4.4.2), лямбда-выражение должно присваиваться в конструкторе класса. В случае использования классов STL (п. 4.5.1) лямбда-выражение передается подобно любому другому аргументу. Подробно эти вопросы рассматриваются в разделе 4, посвященном использованию шаблонов.
2.5.4. Синхронный вызов
Инициатор для синхронного вызова с лямбда-выражением реализуется в виде шаблонной функции, параметром шаблона выступает тип аргумента. Подробно этот вопрос рассмотрен в п. 4.2.1.
2.5.5. Преимущества и недостатки
Преимущества и недостатки реализации обратных вызовов с помощью лямбда-выражения приведены в Табл. 6.
Табл. 6. Преимущества и недостатки обратных вызовов с помощью лямбда-выражения
Гибкое управление контекстом. Возможность захвата переменных предоставляет простые и удобные средства изменения контекста. Изменяя состав захваченных переменных, мы легко можем добавлять значения, необходимые для контекста, при этом нет необходимости изменять код инициатора. Захватив указатель this, мы получаем доступ к содержимому класса, т. е. фактически лямбда-выражение превращается в «метод внутри метода» (см. пример в Листинг 22). Элегантно, не правда ли?
Требует использования шаблонов. Использование шаблонов накладывает архитектурные ограничения на реализацию программных модулей. Это связанно с тем, что шаблоны не предполагают присутствие предварительно откомпилированного кода. Подробнее об этом мы будем говорить в соответствующей главе (4.7), посвященной ограничениям при использовании шаблонов.
>class EventCounter
>{
>public:
> void AddEvent(unsigned int event)
> {
> callCounter_++;
> lastEvent_ = event;
> }
>private:
> unsigned int callCounter_ = 0;
> int lastEvent_ = 0;
>};
>class Executor
>{
>public:
> Executor(EventCounter* counter): counter_(counter)
> {
> auto lambda = [this](int eventID)
> {
> //It will be called by initiator
> counter_->AddEvent(eventID);
> processEvent(eventID);
> };
> //Setup lambda in initiator
> }
>private:
> EventCounter* counter_;
> void processEvent(int eventID) {/*Do something*/}
>};
2.6. Итоги
В C++ обратные вызовы могут быть реализованы с помощью следующих конструкций:
• указатель на функцию;
• указатель на статический метод класса;
• указатель на метод-член класса;
• функциональный объект;
• лямбда-выражение.
Каждая реализация имеет свои достоинства и недостатки. Так какую все-таки выбрать? Чтобы ответить на этот вопрос, необходимо выполнить сравнительный анализ.
3. Сравнительный анализ реализаций
3.1. Методологические подходы
3.1.1. Обобщенный алгоритм
Итак, мы рассмотрели различные способы реализации обратных вызовов. Какая из них наилучшим образом подходит для использования в конкретной ситуации? Чтобы ответить на этот вопрос, необходимо сравнить реализации, т. е. требуется сравнительный анализ.
Обобщенный алгоритм сравнительного анализа включает следующие шаги.
1. Выбрать объекты анализа.
2. Определить критерии сравнения.
3. Построить матрицу соответствия, в которой отобразить, насколько объекты анализа соответствуют выбранным критериям.
4. Проанализировать полученные результаты и выбрать объект, наилучшим образом удовлетворяющий совокупности критериев.