С++ lambda capture по значению

Я читаю раздел лямбда С++ в главе 3 этой книги, и следующий код меня смущает:

int x = 0;
int y = 42;
auto qqq = [x, &y] {
    std::cout << "x: " << x << std::endl;
    std::cout << "y: " << y << std::endl;
    ++y;
};
x = y = 77;
qqq();
qqq();
std::cout << "final y: " << y << std::endl;

Этот код печатает:

x: 0
y: 77
x: 0
y: 78
final y: 79

Почему qqq() не регистрирует, что x изменился на 77? Было указано, что передача по значению означает, что мы можем читать, но не изменять данные, доступные для чтения, где была определена лямбда. Означает ли это, что мы не можем видеть изменения после определения?

Ответ 1

Это потому, что переменная захватывается значением (т.е. скопировано) только один раз, когда вы определяете лямбда. Он не "обновляется", как вы можете поверить. Код примерно эквивалентен:

#include <iostream>

int x = 0;
struct Lambda
{
    int _internal_x; // this is used to "capture" x ONLY ONCE
    Lambda(): _internal_x(x) {} // we "capture" it at construction, no updates after
    void operator()() const
    {
        std::cout << _internal_x << std::endl;
    }
} qqq; 

int main()
{
    qqq(); 
    x = 77; // this has no effect on the internal state of the lambda
    qqq();
}

Live on Coliru

Ответ 2

Связывая переменную по значению с лямбда-замыканием, вы фактически копируете значение переменной в отдельную переменную, которая находится внутри лямбда-объекта. Аналогично, связывая переменную по ссылке, вы делаете такую ​​внутреннюю переменную вместо ссылки на исходную переменную, тем самым имея возможность "видеть изменения" в исходной переменной.

Посмотрите на это так. Следующий код...

#include <iostream>

int main()
{
    int x = 0;
    int y = 42;
    auto qqq = [x, &y] {
        std::cout << "x: " << x << std::endl;
        std::cout << "y: " << y << std::endl;
        ++y;
    };
    x = y = 77;
    qqq();
    qqq();
    std::cout << "final y: " << y << std::endl;
}

Является (вроде, но не совсем) синтаксическим сахаром для...

#include <iostream>

class MyLambda
{
    private:
        int x;
        int& y;

    public:
        MyLambda(int x, int& y) : x(x), y(y) {}

        void operator()()
        {
            std::cout << "x: " << x << std::endl;
            std::cout << "y: " << y << std::endl;
            ++y;
        }
};

int main()
{
    int x = 0;
    int y = 42;
    MyLambda qqq = MyLambda(x, y);
    x = y = 77;
    qqq();
    qqq();
    std::cout << "final y: " << y << std::endl;
}

За исключением специального синтаксиса лямбда и того факта, что вы не можете напрямую ссылаться на лямбда-тип. Сравнивая первое с более поздним кодом, вы должны уметь выучить свое понимание закрытия.

Ответ 3

lambda использует значение во время определения лямбда (оно делает копию), а не во время его вызова.

Это может помочь: Как реализованы Lambda Closures?