MyTetra Share
Делитесь знаниями!
Как лямбда замыкает на себя переменные, если она была присвоена переменной и вызвана из другого контекста?
Время создания: 19.02.2025 11:34
Текстовые метки: c++, си++, лямбда, функция, замыкание, переменная, захват, контекст
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/1739954072y2d4emludj/text.html на raw.github.com

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


Ответ: Когда лямбда-выражение замыкает (захватывает) переменные из окружающего контекста, его поведение зависит от способа захвата:


  1. Захват по значению ([=] или [x])
    – Лямбда создаёт копию переменной.
    – Даже если оригинальная переменная выйдет из области видимости, лямбда будет просто использовать её копию.
  2. Захват по ссылке ([&] или [&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, если нужно гарантировать корректное обращение к данным.


Так же в этом разделе:
 
MyTetra Share v.0.67
Яндекс индекс цитирования