Управление памятью для лямбды в C++11

Может ли кто-нибудь описать, почему этот код не работает (на ошибках GCC4.7.3 до возврата из вызова)?

#include <iostream>
#include <functional>
#include <memory>

using namespace std;

template<typename F>
auto memo(const F &x) -> std::function<decltype(x())()> {
    typedef decltype(x()) return_type;
    typedef std::function<return_type()> thunk_type;
    std::shared_ptr<thunk_type> thunk_ptr = std::make_shared<thunk_type>();

    *thunk_ptr = [thunk_ptr, &x]() {
        cerr << "First " << thunk_ptr.get() << endl;
        auto val = x();
        *thunk_ptr = [val]() { return val; };
        return (*thunk_ptr)();
    };

    return [thunk_ptr]() { return (*thunk_ptr)(); };
};

int foo() {
    cerr << "Hi" << endl;
    return 42;
}

int main() {
    auto x = memo(foo);
    cout << x() << endl ;
    cout << x() << endl ;
    cout << x() << endl ;
};

Мои оригинальные предположения:

  1. каждый std::function<T()> это своего рода ссылка /shared_ptr на некоторый объект, который представляет закрытие. Т.е. срок жизни подобранной стоимости этим ограничен.
  2. std::function<T()> У объекта есть оператор присваивания, который откажется от старого замыкания (окончание выбранных значений времени жизни) и станет владельцем нового значения.

PS Этот вопрос возник после того, как я прочитал вопрос о ленивых в C++11

1 ответ

Решение

Это проблемный код:

[thunk_ptr, &x]() {
    auto val = x();
    *thunk_ptr = [val]() { return val; };
    return (*thunk_ptr)(); // <--- references a non-existant local variable
}

Проблема в том, что местный thunk_ptr это копия из контекста. То есть в задании *thunk_ptr = ... thunk_ptr ссылается на копию, принадлежащую объекту функции. Однако с присвоением объект функции перестает существовать. То есть на следующей строке thunk_ptr относится к только что уничтоженному объекту.

Существует несколько подходов к решению проблемы:

  1. Вместо того, чтобы придумывать, просто вернитесь val, Проблема здесь в том, что return_type может быть ссылочным типом, который приведет к сбою этого подхода.
  2. Вернуть результат прямо из задания: до назначения thunk_ptr еще жив, и после назначения он все еще возвращает ссылку на std::function<...>() объект:

    return (*thunk_ptr = [val](){ return val; })();
    
  3. Безопасная копия thunk_ptr и использовать эту копию для вызова объекта функции в return заявление:

    std::shared_ptr<thunk_type> tmp = thunk_ptr;
    *tmp = [val]() { return val; };
    return (*tmp)();
    
  4. Сохранить копию ссылки на std::function и используйте его вместо ссылки на поле, которое принадлежит перезаписанному закрытию:

    auto &thunk = *thunk_ptr;
    thunk = [val]() { return val; };
    return thunk();
    
Другие вопросы по тегам