C 11 lambda — зачем мне нужно записывать переменные автоматической длительности?

#c #c 11 #lambda

#c #c 11 #лямбда

Вопрос:

При написании лямбда-функции в части захвата ‘[]’ мне нужно указать только переменные автоматической длительности, в то время как глобальные и статические переменные используются в лямбда-функции без необходимости захвата. Почему это так? Почему мы не можем использовать переменные автоматической длительности так же, как глобальные и статические?

Комментарии:

1. Автоматическая переменная может быть уничтожена, пока лямбда все еще существует.

2. Что вы имеете в виду? Уничтожено во время выполнения lambda? Не могли бы вы предоставить и пример для этого?

3. Почему бы вам не опубликовать свой код таким образом, чтобы можно было определить время жизни компонентов лямбды

4. @Brian это на самом деле не имеет значения. Это также может произойти, если вы фиксируете по ссылке.

5. @jalf Это была именно моя точка зрения…

Ответ №1:

Поскольку лямбда определяет отдельную область видимости: это эквивалентно этому:

 int global_i;

struct Lambda {
    Lambda(int captured_j) : captured_j(captured_j) {}
    void operator()(){
        // in this scope, global_i is accessible, and by capturing auto_j, we make that visible as well, but we can't see auto_i
    }

    int captured_j;
}

void foo() {
    int auto_i;
    int auto_j
    // this lambda
    [j](){}
    // is just shorthand for this:
    Lambda lambda(j);
}
  

Лямбда преобразуется компилятором в объект функции (как Lambda класс в моем примере). И объект функции не имеет доступа к локальным переменным, объявленным в области видимости, в которой был создан экземпляр объекта функции. Если вы не «захватите» их, передав их конструктору.

Что касается того, почему компилятор не делает этого неявно, не требуя от вас запроса, он не может, потому что он не знает, хотите ли вы захватить по значению или по ссылке. В языках GC’ed вы всегда записываете по ссылке, потому что это не имеет значения — объекты, на которые вы ссылаетесь, остаются живыми до тех пор, пока они вам нужны.

Но в C вам нужно управлять временем жизни этих объектов, и если лямбда-выражение всегда записывается по ссылке, оно будет практически бесполезным за пределами области, в которой оно было объявлено (потому что оно будет содержать ссылки на объекты, которые были уничтожены). Итак, в C вы должны указать, хотите ли вы записывать по значению или по ссылке. И поскольку вы должны указать это, компилятор не может просто сделать это за вас.

Комментарии:

1. ОК. Но что такое foo() содержит статическую переменную? Поскольку лямбда-захват не требует явного захвата для статической переменной, как это будет видно в void Lambda::operator()?

Ответ №2:

Рассмотрим эту простую функцию:

 std::function<foo()> f()
{
    foo afoo;

    return [=](){ return afoo; };
}

int main()
{
    auto l = f();

    l();
}
  

Если вы не захватите переменную afoo , она выйдет из области видимости до того, как будет использовано замыкание, созданное lamda!

Также обратите внимание, что я использовал захват по значению точно по той же причине: возврат ссылок / указателей на локальные переменные имеет неопределенное поведение.

Комментарии:

1. А? При [=] захвате по значению не имеет значения , что afoo выходит за рамки. Конечно, было бы проблемой, если бы вы использовали [amp;] захват по ссылке, но это имеет мало общего с вопросом OP о том, почему локальные переменные не просто видны внутри лямбда

2. @jalf «С [=] вами захватывается по значению, поэтому не имеет значения, что afoo выходит за рамки» — вы только что повторили ответ Ману

Ответ №3:

По сути, в лямбде нет особой магии; просто считайте это анонимным функтором.

Обычный функтор также не будет знать о локальных переменных, в которых он хранится, вам нужно будет предоставить их его ctor. Такая анонимная вещь, как лямбда, не имеет настраиваемого c’tor, поэтому вы предоставляете контекст с помощью этого синтаксиса «[]».

Или рассмотреть

 const float g_foo = 42.f;

struct A {
    float _bar;
    struct B {
        B() : _baz(_bar /*oops*/) {}
        float _baz;
    };
};
  

в закрытии A:: B нет понятия A «_bar», но g_foo будет известен.

Комментарии:

1. Опять же, как насчет статических объектов, которые объявлены локально? Почему их не нужно записывать в lambda?

2. Они находятся в области видимости для объявления этого анонимного функтора; например, «_bar» A входит в область видимости B, когда вы квалифицируете его как статический.