Табл. 6. Преимущества и недостатки обратных вызовов с помощью лямбда-выражения


Гибкое управление контекстом. Возможность захвата переменных предоставляет простые и удобные средства изменения контекста. Изменяя состав захваченных переменных, мы легко можем добавлять значения, необходимые для контекста, при этом нет необходимости изменять код инициатора. Захватив указатель this, мы получаем доступ к содержимому класса, т. е. фактически лямбда-выражение превращается в «метод внутри метода» (см. пример в Листинг 22). Элегантно, не правда ли?

Требует использования шаблонов. Использование шаблонов накладывает архитектурные ограничения на реализацию программных модулей. Это связанно с тем, что шаблоны не предполагают присутствие предварительно откомпилированного кода. Подробнее об этом мы будем говорить в соответствующей главе (4.7), посвященной ограничениям при использовании шаблонов.

Листинг 22. Лямбда-выражение с захватом указателя this

>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. Проанализировать полученные результаты и выбрать объект, наилучшим образом удовлетворяющий совокупности критериев.


Рассмотрим указанные шаги подробнее.

1. Объект анализа – это некая сущность, которая будет подвергаться анализу. В нашем случае такими сущностями выступают реализации обратных вызовов.

2. Выбор критериев – пожалуй, самый сложный и в то же время наиболее важный этап сравнительного анализа. Критерии должны отражать значимость показателя, который они определяют; неверный выбор критериев приводит к неправильным результатам. Так, например, в качестве критерия можно выбрать количество строк кода, но насколько этот показатель значим при разработке? В нашем случае совершенно не значим: не имеет значения, займет реализация 10 или 50 строк, важно то, насколько она обеспечивает качество выполняемых функций. Качество, в свою очередь, определяется степенью выполнения требований, предъявляемых к проектируемой системе. По этой причине именно требования наилучшим образом подходят для использования в качестве критериев.

3. Матрица соответствия строится в виде таблицы. В заголовки строк таблицы вписываются критерии, в заголовки столбцов – объекты анализа. В ячейках таблицы для каждой пары «объект-критерий» выставляется степень соответствия объекта заданному критерию. Степень выполнения может быть качественной (выполняется/не выполняется) или количественной (выставляется оценка по заданной шкале).