Захват внешних значений в лямбда-выражениях

Последнее обновление: 25.03.2023

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

Получение данных по значению

Если надо получить все внешние переменные из области, где определено лямбда-выражение, по значению, то в квадратных скобках указывается символ "равно" =. Но в этом случае в лямбда-выражении значения внешних переменных изменить нельзя:

#include <iostream>

int main()
{
    int n{10};
    auto add = [=](int x)  { std::cout << x + n << std::endl; };
    add(4); // 14
}

Благодаря выражению [=] лямбда может получить внешнюю переменную n и использовать ее значение.

Для подобного лямбда-выражения компилятор будет генерировать класс наподобие:

class __Lambda1c8
{
public:
	__Lambda1c8(const int& arg1) : n(arg1) {}
 	auto operator()(int x) const
 	{
 		std::cout << x + n << std::endl;
 	}
private:
	int n;
};

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

Стоит отметить, что хотя мы не можем изменить внешнюю переменную, но мы можем передать по значению указатель на внешнюю переменную и через этот указатель внутри лямбда-выражения изменить значение переменной

#include <iostream>

int main()
{
    int n{10};
    int* np {&n};
    auto increment = [np](){(*np)++;};
    increment(); // n = 10
    std::cout << "n = " << n << std::endl;   // n = 11
}

Получение данных по ссылке

Если надо получить внешние переменные по ссылке, то в квадратных скобках указывается символ амперсанда &. В этом случае лямбда-выражение может изменять значения этих переменных:

#include <iostream>

int main()
{
    int n{10};
    auto increment = [&]()  { 
        n++;    // увеличиваем значение внешней переменной n
        std::cout << "n inside lambda: " << n << std::endl; 
    };
    increment();
    std::cout << "n outside lambda: " << n << std::endl; 
}

Благодаря выражению [&] лямбда increment может получить внешнюю переменную n по ссылке и изменять ее значение. В данном случае увеличиваем переменную n на единицу. И по консольному выводу мы можем увидить, что n в лямбда-выражении и внешняя переменная n фактически представляют одно и то же значение:

n inside lambda: 11
n outside lambda: 11

Для подобного лямбда-выражения компилятор будет генерировать класс наподобие:

class __Lambda1c8
{
public:
	__Lambda1c8(int& arg1) : n(arg1) {}
 	auto operator()() const
 	{
		n++;
 		std::cout << "n inside lambda: " << n << std::endl; 
 	}
private:
	int& n;
};

Хотя оператор (), также как и в предыдущем случае, определен как константный, но поскольку внешняя переменная сохраняется как ссылка, то мы можем через эту ссылку изменить ее значение.

mutable-параметры

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

#include <iostream>

int main()
{
    int n{10};
    auto increment = [=]() mutable  { 
        n++;    // увеличиваем значение внешней переменной n
        std::cout << "n inside lambda: " << n << std::endl; 
    };
    increment();
    std::cout << "n outside lambda: " << n << std::endl; 
}

Здесь внешняя переменная n передается по значению, и мы ее изменить не можем, но мы можем изменить копию этого значения, которое используется внутри лямбды, что нам и покажет консольный вывод:

n inside lambda: 11
n outside lambda: 10

Получение определенных переменных

По умолчанию выражения [=]/[&] позволяют захватить все переменные из окружения. Но также можно захватить только определенные переменные. Чтобы получить внешние переменные, применяется выражение [&имя_переменной]:

#include <iostream>

int main()
{
    int n{10};
    // получаем внешнюю переменную n по ссылке
    auto increment = [&n](){ n++;};
    increment();
    std::cout << "n: " << n << std::endl;   // n = 11
}

Если надо получить внешнюю переменную по значению, то просто указываем ее имя в квадратных скобках:

#include <iostream>

int main()
{
    int n{10};
    // получаем внешнюю переменную n по значению
    auto increment = [n](){ std::cout << "n: " << n << std::endl;};
    increment(); // n = 10
}

Если надо захватить несколько переменных, то они указываются через запятую. Если переменная передается по ссылке, то перед ее именем указывается амперсанд:

[&k, l, &m, n]		// k и m - по ссылке, l и n - по значению

Можно передать все по значению и лишь некоторые по ссылке

[=, &m, &n]		// все по значению, а m и n - по ссылке

Или, наоборот, передать все по ссылке и лишь некоторые по значению

[&, m, n]		// все по по ссылке, а m и n - по значению

Захват членов класса

Для обращения к членам класса - переменным и функциям (вне зависимости приватным или публичным) применяется выражение [this]:

#include <iostream>

void printer(auto func)
{
    std::cout << "*******************"<< std::endl;
    func();
    std::cout << "*******************"<< std::endl;
}
class Message
{
public:
    Message(const std::string& text): text{text}
    {}
    void print()
    {
        printer([this](){ std::cout << text << std::endl;});
    }
private:
    std::string text;
};

int main()
{
    Message hello{"Hello World"};
    hello.print();
}

В функции print класса Message выводим на консоль текст сообщения, хранимое в переменной text. Для вывода применяется внешняя функция printer, которая осуществляет некоторое декоративное оформление и выполняет функцию, передаваемую в качестве параметра. В качестве этой функции в данном случае применяется лямбда-выражение, которое обращается к переменной text класса.

Вместе с указателем this можно захватывать и другие переменные окружения. Для этого this можно комбинировать с & или = и захватом отдельных переменных. Например, [=, this], [this, &n] и [x, this, &n]

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850