Вопрос: если лямбда может замыкать при своем использовании переменные из контекста, то что будет происходить с этими переменными, если лямбда была присвоена переменной и вызвана через переменную из совершенно другого контекста?
Ответ: Когда лямбда-выражение замыкает (захватывает) переменные из окружающего контекста, его поведение зависит от способа захвата:
- Захват по значению ([=] или [x])
– Лямбда создаёт копию переменной.
– Даже если оригинальная переменная выйдет из области видимости, лямбда будет просто использовать её копию.
- Захват по ссылке ([&] или [&x])
– Лямбда использует ссылку на переменную.
– Если переменная выйдет из области видимости, доступ к ней приведёт к неопределённому поведению (UB).
Пример 1: Захват по значению (безопасный)
#include <iostream>
#include <functional>
std::function<void()> getLambda() {
int x = 10;
return [x]() { std::cout << "Captured x: " << x << std::endl; };
} // x выходит из области видимости, но в лямбде хранится копия
int main() {
auto func = getLambda();
func(); // Выведет: Captured x: 10 (копия x сохранилась)
}
- этот код безопасен, т. к. переменная x передаётся по значению, поэтому сохраняется в лямбде.
Пример 2: Захват по ссылке (опасный)
#include <iostream>
#include <functional>
std::function<void()> getLambda() {
int x = 10;
return [&x]() { std::cout << "Captured x: " << x << std::endl; };
} // x уничтожается
int main() {
auto func = getLambda();
func(); // НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ! x уже не существует
}
- этот код ошибочен. Переменная x захватывается по ссылке, но при выходе из getLambda() уничтожается, и получается что лямбда хранит "мертвую" ссылку.
Чтобы избежать такой проблемы, можно пользоваться умным указателем std::shared_ptr.
Пример 3: Захват std::shared_ptr (безопасный способ хранения)
#include <iostream>
#include <memory>
#include <functional>
std::function<void()> getLambda() {
auto ptr = std::make_shared<int>(10);
return [ptr]() { std::cout << "Captured ptr: " << *ptr << std::endl; };
} // ptr уничтожается, но лямбда хранит копию shared_ptr
int main() {
auto func = getLambda();
func(); // Выведет: Captured ptr: 10 (указатель жив)
}
- этот код безопасен, так как shared_ptr как бы продлевает жизнь объекта. Происходит это так: вначале через std::make_shared в куче создается копия значения, которое будет использоваться в лямбде. Затем сама лямбда захватывает умный указатель ptr по значению (ptr будет иметь тип std::shared_ptr<int>). Даный умный указатель будет разрушен и соответствующие ему данные будут удалены из кучи в тот момент, когда исчезнет лямбда.
Итого
Следует запомнить следующие особенности:
- Если происходит захват переменных по значению ([=]), данные копируются, что безопасно.
- Если происходит захват переменных по ссылке ([&]), лямбда использует оригинальную переменную, что опасно, если лямбда выполняется вне области существования переменной.
- Лучше всего использовать std::shared_ptr, если нужно гарантировать корректное обращение к данным.