2016-12-26 6 views
4

РезюмеC++ лямбда лексического замыкание над локальными переменными

В C++, когда я вернусь лямбда из функции, которая захваченной локальной переменной этой функции, что конкретно происходит, и почему? Компилятор (g ++), похоже, позволяет это, но это дает мне разные результаты, чем я ожидал, поэтому я не уверен, что это технически безопасно/поддерживается.

Подробности

В некоторых языках (Swift, Lisp и т.д.) вы можете захватить локальные переменные в замыкания/лямбда, и они остаются в силе и в объеме, пока крышка находится в области (I «Я слышал, что он называется« лямбда над let lambda »в контексте Lisp). Например, в Swift, пример кода для того, что я пытаюсь сделать, это:

func counter(initial: Int) -> (() -> Int) { 
    var count = initial 
    return { count += 1; return count } 
} 

let c = counter(initial: 0) 
c() // returns 1 
c() // returns 2 
c() // returns 3 

Я попытался написать C++ эквивалент этого, как в следующем:

auto counter(int initial) 
{ 
    int count = initial; 
    return [&count]() -> int { 
     count = count + 1; 
     return count; 
    }; 
} 

Однако результат Я получаю:

auto c = counter(0); 
std::cout << c() << std::endl; // prints 1 
std::cout << c() << std::endl; // prints 1 
std::cout << c() << std::endl; // prints 1 

Если я захвачу переменной, которая до сих пор в рамках, он работает, как я ожидал. Например, если я все следующие в одной функции:

int count = 0; 
auto c = [&count]() -> int { 
    count = count + 1; 
    return count; 
}; 
std::cout << c() << std::endl; // prints 1 
std::cout << c() << std::endl; // prints 2 
std::cout << c() << std::endl; // prints 3 

Так что я думаю, мой вопрос, в первую C++, например, выше, чем на самом деле получение захватили? И определяется ли это поведение, или я просто имею ссылку на случайную память в стеке?

+0

Я не понимаю эту строку 'auto c = counter();'. Как вы можете вызвать функцию без аргумента? Похоже, это даже не компилируется. – Richard

+0

@Richard, исправленный, извините за это. –

+0

В первом случае у вас есть ссылка на случайную память в стеке. – user1438832

ответ

8
return [&count]() -> int { 

Это захват по ссылке. Лямбда фиксирует ссылку на этот объект.

Объект, о котором идет речь, count, находится в локальной области функции, поэтому, когда функция возвращается, count уничтожается, и это становится ссылкой на объект, который вышел из сферы действия и уничтожен. Использование этой ссылки становится неопределенным поведением.

Захватив по значению, кажется, чтобы решить эту проблему:

return [count]() -> int { 

Но ваше явное намерение так, что каждый вызов этого лямбда возвращает монотонно возрастающее значение счетчика. И просто захватить объект по стоимости недостаточно.Кроме того, необходимо использовать mutable lambda:

return [count]() mutable -> int 
{ 
    return ++count; 
}; 

Но педантичный ответ на ваш вопрос «что происходит», что лямбда является по существу анонимный класс, а что лямбда захватывает действительно члены класса. Ваша лямбда-эквивалентна:

class SomeAnonymousClassName { 

    int &count; 

public: 
    SomeAnonymousClassName(int &count) : count(count) 
    {} 

    int operator() 
    { 
      // Whatever you stick in your lambda goes here. 
    } 
}; 

Захватив что-то по ссылке приводит к члену класса, это ссылка. Захват чего-то по значению переводит на члена класса, который не является ссылкой, и акт захвата лямбда-переменных переводит их в конструктор лямбда-класса, что и происходит при создании лямбда. Лямбда - это действительно анонимный класс с определенным operator().

В обычной лямбда operator() фактически является методом оператора const. В изменчивой лямбда operator() представляет собой не-const, изменяемый операторный метод.

+0

Извините, я не знаю, почему мое редактирование ошибочно удалит ваш последний абзац – Danh

+0

Наши исправления столкнулись, неважно. –

+0

Ahh прохладно. Спасибо за хорошее объяснение и указатель на изменчивые лямбды. –

0

В вашем первом коде вы возвращаете лямбду, которая захватывает переменную по ссылке и вызывает ее снаружи, она вызывает неопределенное поведение из-за оборванной ссылки. После выхода из функции ссылочная переменная умирает, а возвращаемая лямбда не работает.

Вы можете передать initail значение по ссылке в первую очередь:

auto counter(int &count) 
{ 
    return [&count]() -> int { 
     count = count + 1; 
     return count; 
    }; 
} 

int main() 
{ 
    int initial = 0; 
    auto c = counter(initial); 
    std::cout << c() << std::endl; // prints 1 
    std::cout << c() << std::endl; // prints 2 
    std::cout << c() << std::endl; // prints 3 
} 
0

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