Ответ
Поля (или захват переменных) позволяют лямбда-функции в C++ получать доступ к данным из окружающей области видимости. Это ключевой механизм для создания замыканий.
Основные способы захвата:
- По значению
[=]или[x]: Создаётся копия переменной на момент создания лямбды. Изменения внутри лямбды не затрагивают оригинал. - По ссылке
[&]или[&x]: Лямбда работает непосредственно с исходной переменной. Изменения внутри лямбды видны снаружи. - Инициализирующий захват (C++14)
[x = std::move(obj)]: Позволяет перемещать или инициализировать захватываемые объекты.
Пример:
int counter = 0;
std::string msg = "Hello";
// Захват counter по ссылке, msg по значению
auto lambda = [&counter, msg]() {
++counter; // Меняет исходный counter
std::cout << msg << " called " << counter << " timesn"; // msg - копия
};
lambda(); // Вывод: Hello called 1 times
lambda(); // Вывод: Hello called 2 times
// Исходная msg осталась "Hello"
Критические нюансы:
- Время жизни: Захват по ссылке опасен, если лямбда (например, сохранённая в
std::function) переживёт захваченную переменную, что приведёт к неопределённому поведению. - По умолчанию
const: Лямбда, захватившая переменные по значению, по умолчанию являетсяconst. Для модификации таких копий требуется спецификаторmutable. this: Для захвата членов класса внутри метода используется[this]или[*this](C++17, захват копии объекта).
Ответ 18+ 🔞
Давай разберём эту тему про захват переменных в лямбдах, а то тут, бля, столько подводных камней, что можно на ровном месте овердохуища проблем нахватать. Смотри, в чём суть.
Представь, что лямбда — это такой хитрожопый зверёк, который живёт внутри твоего кода. И чтобы он мог работать с данными извне, ему нужно их как-то доставить. Вот этот самый процесс доставки и есть захват. А способов доставки — несколько, и каждый со своими, ёпта, последствиями.
Основные способы, как этот зверёк может хапнуть переменные:
- По значению
[=]или, например,[x]: Тут всё просто. Зверёк делает себе личную, отдельную копию переменной. Он с ней там внутри делает что хочет, но на оригинал это нихуя не влияет. Как будто ты сфоткал документ и рисуешь усы на фотке — оригинальный паспорт остаётся чистым. - По ссылке
[&]или[&x]: А вот это уже интереснее. Зверёк не берёт копию, а просто смотрит на оригинальную переменную, как в окошко. И если он её внутри поменяет — то поменяется она везде. Удобно, но, бля, опасно. Представь, что дом (переменная) снесли, а зверёк всё ещё тычется пальцем в то место, где было окошко. Полный пиздец и неопределённое поведение. - Инициализирующий захват (это с C++14 пошло)
[x = std::move(obj)]: Это уже для продвинутых. Позволяет не просто скопировать, а, например, переместить ресурс прямо в лямбду, или вообще какую-то хуйню на месте сконструировать. Мощная штука, но надо понимать, что делаешь.
Смотри на примере, как это выглядит в деле:
int counter = 0;
std::string msg = "Hello";
// Захватываем counter по ссылке, а msg — по значению
auto lambda = [&counter, msg]() {
++counter; // Меняем исходный counter, потому что взяли на него ссылку
std::cout << msg << " called " << counter << " timesn"; // msg у нас своя, копия
};
lambda(); // Выведет: Hello called 1 times
lambda(); // Выведет: Hello called 2 times
// Исходная msg так и осталась "Hello", её не тронули
Вроде бы всё логично, да? Но вот где собака, сука, порылась — в нюансах, от которых волосы дыбом встают.
- Время жизни — это пиздец какой важный момент. Захватил переменную по ссылке, лямбду куда-то сохранил (в
std::function, например), а переменная-то уже померла и накрылась медным тазом. А твоя лямбда потом пытается по этой ссылке обратиться — и получает чистейшее неопределённое поведение. Терпения ноль ебать, когда такие баги ищешь. - Захват по значению — не значит «можно менять».
Вот смотри, обычная лямбда, которая что-то захватила по значению — она по умолчанию
const. То есть все эти захваченные копии внутри неё — константные. Хочешь их менять? Так объяви лямбдуmutable, а то компилятор тебе такое впендюрит, что мало не покажется. - Захват членов класса — отдельная песня.
Если ты внутри метода класса пишешь лямбду и хочешь достучаться до полей этого самого класса, то старый способ — это захватить
[this]. Лямбда получит указатель и сможет лазить по всем полям объекта. Но опять же, следи за временем жизни объекта! А с C++17 появился вариант получше —[*this]. Это уже захват копии всего объекта по значению. Дороговато может быть, зато безопаснее, если твоя лямбда должна пережить тот экземпляр класса, в котором родилась.
Короче, чувак, инструмент мощный, но, как говорится, вы ходите по охуенно тонкому льду. Один неверный захват по ссылке — и будет вам хиросима с нагасаки в одном флаконе. Думай, что и зачем захватываешь, и всё будет пизда рулю.