Где хранятся лямбда-захваченные переменные?
Как это работает? Он печатает 6
:
#include <iostream>
#include <functional>
using namespace std;
void scopeIt(std::function<int()> &fun) {
int val = 6;
fun = [=](){return val;}; //<-- this
}
int main() {
std::function<int()> fun;
scopeIt(fun);
cout << fun();
return 0;
}
Где хранится значение 6
, сохраненное после того, как scopeIt
вызывается? Если я заменяю [=]
на [&]
, он печатает 0
вместо 6
.
Ответы
Ответ 1
Он хранится в закрытии, который - в вашем коде - сохраняется в std::function<int()> &fun
.
Лямбда генерирует то, что эквивалентно экземпляру сгенерированного класса компилятора.
Этот код:
[=](){return val;}
Создает то, что фактически эквивалентно этому... это будет "закрытие":
struct UNNAMED_TYPE
{
UNNAMED_TYPE(int val) : val(val) {}
const int val;
// Above, your [=] "equals/copy" syntax means "find what variables
// are needed by the lambda and copy them into this object"
int operator() () const { return val; }
// Above, here is the code you provided
} (val);
// ^^^ note that this DECLARED type is being INSTANTIATED (constructed) too!!
Ответ 2
Lambdas в С++ - это действительно "анонимные" функции структуры. Поэтому, когда вы пишете это:
int val = 6;
fun = [=](){return val;};
Что компилятор переводит в это:
int val = 6;
struct __anonymous_struct_line_8 {
int val;
__anonymous_struct_line_8(int v) : val(v) {}
int operator() () const {
return val; // returns this->val
}
};
fun = __anonymous_struct_line_8(val);
Затем std::function
сохраняет этот функтор с помощью типа стирания.
Когда вы используете [&]
вместо [=]
, он меняет структуру на:
struct __anonymous_struct_line_8 {
int& val; // Notice this is a reference now!
...
Итак, теперь объект хранит ссылку на объект функции val
, который становится зависающей (недопустимой) ссылкой после выхода функции (и вы получаете поведение undefined).
Ответ 3
Так называемый тип замыкания (который является типом класса лямбда-выражения) имеет членов для каждого захваченного объекта. Эти элементы являются объектами для захвата по значению и ссылками для захвата по ссылке. Они инициализируются захваченными объектами и независимо друг от друга живут в закрывающем объекте (конкретном объекте типа закрытия, который обозначается этой лямбдой).
Неименованный элемент, который соответствует захвату значения val
, инициализируется с помощью val
и доступен изнутри типов закрытия operator()
, что отлично. Объект закрытия может быть легко скопирован или перемещен несколько раз до тех пор, пока это не произойдет, и что слишком мелкие типы закрытия неявно определили конструкторы перемещения и копирования, как это делают обычные классы.
Однако при захвате по ссылке преобразование lvalue-to-rvalue, которое неявно выполняется при вызове fun
в main
, вызывает поведение undefined как объекта, ссылка на упомянутый ссылочный элемент уже была уничтожена - т.е. мы используем ссылку на свинец.
Ответ 4
Значение лямбда-выражения является объектом типа класса, а
Для каждого объекта захваченный копией, в типе замыкания объявляется неназванный нестатический элемент данных.
([expr.prim.lambda]/14 в С++ 11)
То есть объект, созданный лямбдой
[=](){return val;}
фактически содержит нестатический член типа int
, значение которого равно 6, и этот объект копируется в объект std::function
.