Лямбда-выражения

Начиная со стандарта C++11, помимо обычных и шаблонных функций язык C++ позволяет использовать анонимные функции, которые называются лямбда-выражениями или лямбда-функциями. Лямбда-выражение описывается по следующей схеме:

[ [Захват] ]( [Параметры через запятую] ) [mutable] [noexcept] 
   [ -> Тип_результата] {
      Тело_лямбда-выражения
      [return Возвращаемое_значение;]
   }

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

  • указатель на функцию. Давайте создадим лямбда-выражение, выводящее сообщение в окно консоли, сохраним указатель на него в переменной, а затем вызовем:
void (*lambda)() = []{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения
  • объект класса function. Прежде чем использовать класс, следует подключить заголовочный файл functional:
#include <functional>

Создание объекта осуществляется по следующим схемам:

std::function<Сигнатура_функции> lambda1(Лямбда-выражение);
std::function<Сигнатура_функции> lambda2 = Лямбда-выражение;
std::function<Сигнатура_функции> lambda3;
lambda3 = Лямбда-выражение;
std::function<Сигнатура_функции> lambda4 = nullptr;
lambda4 = Лямбда-выражение;

Внутри угловых скобок указывается сигнатура функции, состоящая из типа возвращаемого значения и типов параметров через запятую внутри круглых скобок, например, void() или int(int,int):

std::function<void()> lambda =
              []{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения
  • ключевое слово auto:
auto lambda = []{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения

Описание лямбда-выражения начинается с квадратных скобок, а затем внутри фигурных скобок выводится сообщение. Лямбда-выражение в нашем примере не принимает параметров, поэтому круглые скобки можно не указывать. Хотя можно указать явным образом, если для вас так более привычно:

auto lambda = [](){ std::cout << "OK" << std::endl; };

Лямбда-выражение в нашем примере ничего не возвращает, а только выводит сообщение, поэтому тип возвращаемого значения не указывается. Если внутри фигурных скобок нет оператора return, то компилятор по умолчанию выберет ключевое слово void.

Вызов лямбда-выражения на исполнение осуществляется с помощью круглых скобок, внутри которых можно передать значения, указав их через запятую. Если лямбда-выражение не принимает параметры, то указываются пустые круглые скобки. Можно не сохранять указатель в переменной, а сразу вызвать лямбда-выражение:

[](){ std::cout << "OK" << std::endl; }(); // OK

Создадим лямбда-выражение с одним параметром:

auto lambda = [](const char *s){
   std::cout << s << std::endl;
};
lambda("message"); // message

Тип возвращаемого значения указывается после оператора ->. Напишем лямбда-выражение для сложения двух целых чисел:

std::function<int(int,int)> lambda = nullptr;
lambda = [](int x, int y) -> int { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30

Компилятор может самостоятельно определить тип возвращаемого значения по выражению в операторе return, поэтому тип возвращаемого значения в этом примере можно не указывать:

std::function<int(int,int)> lambda;
lambda = [](int x, int y) { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30

Начиная со стандарта C++14, вместо реального типа параметра можно указать ключевое слово auto, что позволяет при вызове передавать данные произвольных типов (как при использовании шаблонных функций):

auto lambda = [](auto x, auto y) { return x + y; };
std::cout << lambda(10, 20) << std::endl;   // 30
std::cout << lambda(1.5, 2.2) << std::endl; // 3.7
std::cout << lambda(1.5, 2) << std::endl;   // 3.5
std::cout << lambda(1, 2.2) << std::endl;   // 3.2

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

auto lambda = [](int x, int y=5) { return x + y; };
std::cout << lambda(10, 20) << std::endl;   // 30
std::cout << lambda(10) << std::endl;       // 15

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

Листинг 12.30. Передача лямбда-выражения в функцию

#include <iostream>
#include <cstdlib>

int main() {
   const int ARR_SIZE = 5;
   int arr[ARR_SIZE] = {10, 5, 6, 1, 3};
   std::qsort(arr, ARR_SIZE, sizeof(int),
      [](const void *arg1, const void *arg2) -> int {
         if (*(int *)arg1 < *(int *)arg2) return -1;
         if (*(int *)arg1 > *(int *)arg2) return 1;
         return 0;
      });
   for (int i = 0; i < ARR_SIZE; ++i) {
      std::cout << arr[i] << std::endl;
   }
   return 0;
}

Если указываются пустые квадратные скобки, то внутри тела лямбда-выражения мы имеем доступ только к глобальным переменным (можно как получить значение, так и изменить его):

int global_x = 10;
// ...
int x = 11;
auto lambda = [](){
   std::cout << global_x << std::endl; // 20, а не 10 !!!
   global_x = 30;
   // К локальной переменной x нет доступа
   // std::cout << x << std::endl; // Ошибка
};
global_x = 20;
lambda(); // Вызов лямбда-выражения
std::cout << global_x << std::endl; // 30
std::cout << x << std::endl;        // 11

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

auto lambda = [](int x) {
   return [x]() {
      std::cout << x << std::endl; // 10, а не 20
   };
}(global_x);
global_x = 20;
lambda(); // Вызов лямбда-выражения

Мы создали лямбда-выражение с одним параметром, возвращающее вложенное лямбда-выражение. Далее мы сразу вызываем первое лямбда-выражение с помощью круглых скобок и передаем значение глобальной переменной global_x. Чтобы значение параметра x было доступно во вложенном лямбда-выражении, мы указали название локальной переменной внутри квадратных скобок вложенного лямбда-выражения. Такая операция называется захватом переменной из внешней области видимости или замыканием.

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

  • [=] — захват всех локальных переменных из внешней области видимости по значению. Значения сохраняются на момент создания лямбда-выражения, а не на момент вызова. По умолчанию изменять значения внутри лямбда-выражения нельзя. Чтобы иметь такую возможность, нужно дополнительно указать ключевое слово mutable. Следует учитывать, что изменение значения внутри лямбда-выражения не затронет значения одноименной локальной переменной из внешней области видимости:
int x = 10;
auto lambda = [=]() mutable {
   // Значение на момент создания лямбда-выражения
   std::cout << x << std::endl; // 10, а не 20
   // Изменение значения не затронет значения
   // локальной переменной x из внешней области видимости
   x = 30; // Если mutable не указано, то будет ошибка
};
x = 20;
lambda();                       // Вызов лямбда-выражения
std::cout << x << std::endl;    // 20, а не 30
  • [&] — захват всех локальных переменных из внешней области видимости по ссылке. Значения доступны на момент вызова, а не на момент создания лямбда-выражения. Внутри лямбда-выражения можно как получить значение локальной переменной из внешней области видимости, так и изменить его:
int x = 10;
auto lambda = [&]() {
   // Значение на момент вызова лямбда-выражения
   std::cout << x << std::endl; // 20, а не 10
   // Можно изменить значение
   x = 30;
};
x = 20;
lambda();                       // Вызов лямбда-выражения
std::cout << x << std::endl;    // 30, а не 20
  • [x, y] — захват только переменных x и y по значению:
int x = 10, y = 11, z = 22;
auto lambda = [x, y]() mutable {
   // Значения на момент создания лямбда-выражения
   std::cout << x << std::endl; // 10
   std::cout << y << std::endl; // 11
   // std::cout << z << std::endl; // Ошибка, нет доступа
   // Изменение значений не затронет значений
   // локальных переменных x и y из внешней области видимости
   x = 30; y = 40;
};
x = 20;
y = 12;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl;    // 20
std::cout << y << std::endl;    // 12
std::cout << z << std::endl;    // 22
  • [&x, &y] — захват только переменных x и y по ссылке:
int x = 10, y = 11, z = 22;
auto lambda = [&x, &y]() {
   // Значения на момент вызова лямбда-выражения
   std::cout << x << std::endl; // 20
   std::cout << y << std::endl; // 12
   // std::cout << z << std::endl; // Ошибка, нет доступа
   x = 30; y = 40;
};
x = 20;
y = 12;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl;    // 30
std::cout << y << std::endl;    // 40
std::cout << z << std::endl;    // 22
  • [x, &y] — захват только переменных x (по значению) и y (по ссылке);
  • [=, &x, &y] — захват всех локальных переменных из внешней области видимости по значению, кроме переменных x и y, которые захватываются по ссылке:
int x = 10, y = 11, z = 22;
auto lambda = [=, &x, &y]() mutable {
   // Значения на момент вызова лямбда-выражения
   std::cout << x << std::endl; // 20
   std::cout << y << std::endl; // 12
   // Значение на момент создания лямбда-выражения
   std::cout << z << std::endl; // 22
   x = 30;
   y = 40;
   z = 33; // Не изменит значение из внешней области
};
x = 20;
y = 12;
z = 55;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl;    // 30
std::cout << y << std::endl;    // 40
std::cout << z << std::endl;    // 55, а не 33
  • [&, x, y] — захват всех локальных переменных из внешней области видимости по ссылке, кроме переменных x и y, которые захватываются по значению;
  • [this] — захват указателя this внутри класса:
class A {
public:
   A(int x) : x_(x) {}
   void func() {
      [this]() { std::cout << x_ << std::endl; }();
   }
private:
   int x_;
};

Классы мы подробно рассмотрим в следующей главе, а сейчас просто создадим экземпляр класса A и вызовем метод func():

A obj(10);
obj.func(); // 10

Учебник C++ (MinGW-W64)
Учебник C++ (MinGW-W64) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов