6

有人可以描述为什么这段代码不起作用(在从呼叫返回之前的 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 中懒惰的问题后提出的

4

1 回答 1

6

这是有问题的代码:

[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. 与其花哨,不如 return 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();
    
于 2013-10-01T22:38:48.260 回答