2015-01-14 4 views
23

Как возможно, что этот пример работает? Он печатает 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

Просто догадка, поэтому это не отправлено как ответ: вы фиксируете переменную по значению, поэтому ее копируют в datamember функтора, который компилятор генерирует за кулисами (по крайней мере, так это то, как Скотт Майерс объясняет реализацию лямбда). Если вы сделаете запись по ссылке, член этого функтора будет ссылаться на то, что больше не существует. Может быть, UB, я не знаю. – JorenHeit

+0

Некоторые особенности о лямбда, как указал Скотт Мейерс: http://youtu.be/48kP_Ssg2eY (ускоренная перемотка вперед до 16:42) – JorenHeit

ответ

19

Он хранится в закрытии, который - в вашем коде - затем сохраняется в пределах 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

Теперь у меня есть исследования закрытия ... – BWG

+0

@BWG позвольте мне помочь. .. (отредактировано) –

+0

Итак, с немного лучшим процессором, я мог бы создать свой собственный лямбда-макрос? Вот почему я люблю C++. – BWG

2

Значение лямбда-выражения является объектом класса и

Для каждого субъекта снято copy, в типе закрытия объявляется неназванный нестатический элемент данных.

([expr.prim.lambda]/14 в C++ 11)

То есть, объект, созданный лямбда

[=](){return val;} 

фактически содержит не статический член int, значение которого равно 6, и этот объект копируется в объект std::function.

7

Так называемый тип закрытия (который является типом класса выражения лямбда) имеет членов для каждого захваченного объекта. Эти элементы являются объектами для захвата по значению и ссылками для захвата по ссылке. Они инициализируются захваченными объектами и независимо друг от друга живут в объекте закрытия (конкретный объект типа закрытия, который обозначается этой лямбдой).

Неименованный элемент, который соответствует захвату значения val, инициализируется val и доступен изнутри типов замыканий operator(), что хорошо. Объект закрытия может быть легко скопирован или перемещен несколько раз до тех пор, пока это не произойдет, и это тоже хорошо - типы закрытия имеют неявно определенные конструкторы перемещения и копирования, как это делают обычные классы.
Однако при захвате ссылки, преобразование именующих к Rvalue, который неявно выполняется при вызове fun в main вызывает неопределенное поведение как объекта, контрольный элемент упоминается уже уничтожен - то есть мы используем оборванные Справка.

13

Lambdas in C++ - это действительно «анонимные» функции структуры.Поэтому, когда вы пишете это:

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 магазины, функтора через type erasure.

При использовании [&] вместо [=], он изменяет-структуру на:

struct __anonymous_struct_line_8 { 
    int& val; // Notice this is a reference now! 
    ... 

Так что теперь объект сохраняет ссылку на val объект функции, которая становится оборванными (недопустимыми) ссылками после функциональных выходов (и вы получите неопределенное поведение).

+0

В отличие от захвата копией, реализациям даже не требуется декларировать элементы данных при захвате по ссылке ([expr.prim.lambda]/16), поскольку им не нужно предоставлять никаких гарантий на всю жизнь. – user657267

Смежные вопросы