C++ 14/17中的懒惰评价 - 只是lambdas还是期货等?

ein*_*ica 13 c++ lambda lazy-evaluation c++11 std-future

我刚读过:

C++中的延迟评估

并注意到它有点陈旧,大多数答案都与2011年之前的C++有关.这些天我们有语法lambda,甚至可以推断出返回类型,所以懒惰的评估似乎归结为只是传递它们:而不是

auto x = foo();
Run Code Online (Sandbox Code Playgroud)

你执行

auto unevaluted_x = []() { return foo(); };
Run Code Online (Sandbox Code Playgroud)

然后评估您需要的时间/地点:

auto x = unevaluted_x();
Run Code Online (Sandbox Code Playgroud)

似乎没有更多的东西了.但是,其中一个答案建议使用带异步启动的期货.有人可以用C++或更抽象的方式列出为什么/如果期货对懒惰评估工作有重要意义吗?似乎期货很可能会被热切地评估,但简单地说,在另一个线程上,并且可能没有创建它们的任何优先级; 无论如何,它应该依赖于实现,对吧?

另外,还有其他现代C++构造在懒惰评估的背景下有用吗?

ale*_*in0 13

当你写作

auto unevaluted_x = []() { return foo(); };
...
auto x = unevaluted_x();
Run Code Online (Sandbox Code Playgroud)

每当你想要获得价值时(当你打电话时unevaluated_x)它就会被计算出来,浪费了计算资源.因此,为了摆脱这种过度的工作,跟踪lambda是否已经被调用(可能在其他线程中,或者在代码库中的一个非常不同的位置)是一个好主意.为此,我们需要一些围绕lambda的包装器:

template<typename Callable, typename Return>
class memoized_nullary {
public:
    memoized_nullary(Callable f) : function(f) {}
    Return operator() () {
        if (calculated) {
            return result;
        }
        calculated = true;
        return result = function();
    }
private:
    bool calculated = false;
    Return result;
    Callable function;
};
Run Code Online (Sandbox Code Playgroud)

请注意,此代码只是一个示例,并非线程安全.

但不是重新发明轮子,你可以使用std::shared_future:

auto x = std::async(std::launch::deferred, []() { return foo(); }).share();
Run Code Online (Sandbox Code Playgroud)

这需要更少的代码来编写并支持其他一些功能(例如,检查值是否已经计算,线程安全等).

标准[futures.async,(3.2)]中有以下文字:

如果launch::deferred在策略,存储DECAY_COPY(std::forward<F>(f))DECAY_COPY(std::forward<Args>(args))...共享状态中设置.这些副本fargs构成延迟功能.延迟函数的调用的计算结果INVOKE(std::move(g), std::move(xyz)),其中g是的存储值DECAY_COPY(std::forward<F>(f))xyz处于所存储的副本DECAY_COPY(std::forward<Args>(args))....的任何返回值被存储作为结果处于共享状态.从执行延迟函数传播的任何异常都作为异常结果存储在共享状态中.在功能完成之前,共享状态尚未就绪.在引用此共享状态的异步返回对象上第一次调用非定时等待函数(30.6.4)将调用调用等待函数的线程中的延迟函数.评估INVOKE(std::move(g),std::move(xyz))开始后,该功能不再被视为延期.[注意:如果此策略与其他策略一起指定,例如在使用策略值时launch::async | launch::deferred,实现应该在不再能够有效利用并发性时推迟调用或选择策略. - 尾注]

因此,您可以保证在需要之前不会调用计算.

  • @einpoklum“第一次请求结果时,任务在调用线程上执行(延迟评估)” - 引用自http://en.cppreference.com/w/cpp/thread/launch 将在找到a后更新答案标准中的确认。 (2认同)
  • @nm:`printf`和`sin`接受参数,并且`rand`在很大程度上被称为副作用(特别是因为调用它两次通常产生不同的结果).`std :: bind(sin,1)`可能被称为懒惰,将它包装在一次调用适配器中(或者用来记忆`sin`或者使用它的东西)可能有意义也可能没有意义.适配器应该被称为`memoize_nullary`而不是`cached_lambda`,但是:-) (2认同)

Jon*_*eld 5

这里发生了一些事情。

Applicative order评估意味着在将参数传递给函数之前先对其进行评估。 Normal order评估是指在评估参数之前将参数传递给函数。

正常顺序评估的好处是,某些参数永远不会被评估,而某些参数又会被一遍又一遍地评估。

Lazy评估通常是指normal order + memoization。推迟评估,希望根本不需要评估,但是如果需要评估,请记住结果,这样您只需要执行一次即可。重要的是永远不要评估一个术语,记忆是提供此术语的最简单机制。

promise/future模型是不同再次。这里的想法是,一旦有足够的可用信息,就可能在另一个线程中开始评估。然后,您将尽可能长时间地查看结果,以提高其已可用的机会。


promise/future模型与惰性评估具有一些有趣的协同作用。该策略是:

  1. 推迟评估,直到绝对需要结果为止
  2. 开始在另一个线程中进行评估
  3. 做其他事情
  4. 后台线程完成并将结果存储在某处
  5. 初始线程检索结果

当结果由后台线程产生时,可以整齐地引入备忘录。

尽管两者之间具有协同作用,但它们并不是同一概念。