А.5. Лямбда-функции

Лямбда-функции — одно из самых интересных новшеств в стандарте C++11, потому что они позволяют существенно упростить код и исключить многие стереотипные конструкции, которые применяются при написании объектов, допускающих вызов. Синтаксис лямбда-функций в C++11 позволяет определить функцию в той точке выражения, где она необходима. Это отличное решение, например, для передачи предикатов функциям ожидания из класса std::condition_variable (как в примере из раздела 4.1.1), потому что дает возможность кратко выразить семантику в терминах доступных в данной точке переменных, а не запоминать необходимое состояние в переменных-членах класса с оператором вызова.

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

[] { ← Лямбда-выражение начинается с []

 do_stuff();     │ Конец определения

 do_more_stuff();│ лямбда-выражения

} ();           ←┘ и его вызов

В данном случае лямбда-выражение сразу вызывается, потому что за ним следуют круглые скобки, однако это необычно. Ведь если вы хотите вызывать его напрямую, то можно было бы вообще обойтись без лямбда-выражения и записать составляющие его предложения прямо в коде. Чаще лямбда-выражение передаётся в шаблон функции, который принимает допускающий вызов объект в качестве одного из параметров. Но тогда ему, скорее всего, нужны параметры или возвращаемое значение или то и другое вместе. Если лямбда-функция принимает параметры, то их можно указать после лямбда-интродуктора с помощью списка параметров, как для обычной функции. Так, в следующем примере мы выводим все элементы вектора на std::cout, разделяя их символами новой строки:

std::vector<int> data = make_data();

std::for_each(data.begin(), data.end(),

 [](int i){std::cout << i << " ";});

С возвращаемыми значениями всё почти так же просто. Если тело лямбда-функции состоит из единственного предложения return, то тип возвращаемого ей значения совпадает с типом возвращаемого выражения. Например, такую простую лямбда-функцию можно было бы использовать для проверки флага, ожидаемого условной переменной std::condition_variable (см. раздел 4.1.1).

Листинг А.4. Простая лямбда-функция с выводимым типом возвращаемого значения

std::condition_variable cond;

 bool data_ready;

 std::mutex m;

void wait_for_data() {

 std::unique_lock<std::mutex> lk(m);

 cond.wait(lk, []{return data_ready;}); ← (1)

}

Тип значения, возвращаемого лямбда-функцией, которая передана cond.wait() (1), выводится из типа переменной data_ready, то есть совпадает с bool. Когда условная переменная получает сигнал, она вызывает эту лямбда-функцию, захватив предварительно мьютекс, и wait() возвращает управление, только если data_ready равно true.

Но что если невозможно написать тело лямбда-функции, так чтобы оно содержало единственное предложение return? В таком случае тип возвращаемого значения следует задать явно. Это можно сделать и тогда, когда тело функции состоит из единственного предложения return, но обязательно, если тело более сложное. Для задания типа возвращаемого значения нужно поставить после списка параметров функции стрелку (->), а за ней указать тип. Если лямбда-функция не имеет параметров, то список параметров (пустой) все равно необходим, иначе задать тип возвращаемого значения невозможно. Таким образом, предикат, проверяемый условной переменной, можно записать так:

cond.wait(lk, []()->bool{ return data_ready; });

Лямбда-функции с явно заданным типом возвращаемого значения можно использовать, например, для записи сообщений в журнал или для более сложной обработки:

cond.wait(lk, []()->bool {

 if (data_ready) {

  std::cout << "Данные готовы" << std::endl;

  return true;

 } else {

  std::cout <<

   "Данные не готовы, продолжаю ждать" << std::endl;

  return false;

 }

});

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