【问题标题】:What does this C++11 code (memoize) do?这个 C++11 代码 (memoize) 有什么作用?
【发布时间】:2011-07-18 05:03:47
【问题描述】:

我找到了包含此代码的an article

template <typename ReturnType, typename... Args>
std::function<ReturnType (Args...)>
memoize(std::function<ReturnType (Args...)> func)
{
    std::map<std::tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable {
            std::tuple<Args...> t(args...);
            if (cache.find(t) == cache.end())                
                cache[t] = func(args...);
            return cache[t];
    });
}

你能解释一下吗?这里很多东西我看不懂,但最奇怪的是缓存是本地的,不是静态的,但也许我错了……

【问题讨论】:

  • 固定代码格式。在 SO 上,您通过缩进四个空格来表示代码,&lt;code&gt;&lt;pre&gt; 标记用处不大。
  • @Ben 谢谢!我以前用过那个标签,没问题:)

标签: c++ c++11 lambda memoization


【解决方案1】:

这是memoization 的简单 C++1x 实现。

memoize 函数返回一个closure。返回值是一个函数,其状态与通过参数传递的状态不同(在本例中为cache 变量)。

匿名函数中的[=] 位表示返回的函数应该获取所有局部变量的副本。 cache 变量不是静态的,因为它旨在在返回函数的调用之间共享。

因此,对memoize 的每次调用都会返回一个不同的函数以及它自己的cache。对 memoize 返回的特定闭包的后续调用将从 那个 闭包的 cache 插入/获取值。

您可以认为这在某种程度上等同于更老式的 OOP 版本:

template <typename ReturnType, typename... Args>
class Memoize
{
    std::map<std::tuple<Args...>, ReturnType> cache;
public:
    ReturnType operator() (Args... args)
    {
        std::tuple<Args...> t(args...);
        if (cache.find(t) == cache.end())                
            cache[t] = func(args...);
        return cache[t];
    }
};

【讨论】:

  • 不过,为什么这(缓存关闭)比在返回的 lambda 中将缓存声明为静态更好?
  • 进一步,我们如何使返回的记忆函数线程安全?关闭缓存和静态缓存都不能解决这个问题吗?
  • @Nordlöw:问题中使用的样式是我所知道的所有编程语言中状态闭包的常用习语。坚持众所周知的用法是一件好事(tm)。为了使闭包线程安全,您可以使用读写锁 (boost::shared_mutex),它可以在与缓存相同的范围内声明。根据需要在 lambda 中锁定地图。
  • 使用 Intel TBBtbb::concurrent_unordered_map 而不是 boost::shared_mutex 会不会过大?
  • @Nordlöw:至于英特尔 TTB,您应该提出一个单独的问题。至于 boost shared_mutex,请阅读文档。一个简短的评论是在查找时锁定缓存以进行读取访问(共享锁定)并在插入时锁定写入访问(独占锁定)。
【解决方案2】:

缓存嵌入到 lambda 本身中,并且是本地的。

因此,如果您创建两个 lambda,则每个都有自己的缓存。

这是实现简单缓存的好方法,因为这样一来,当 lambda 超出范围时,使用的内存就会被清除,并且不会出现内存爆炸。

【讨论】:

    【解决方案3】:

    “这段简单的代码”也可以记忆递归函数,只要它被正确调用。这里我举一个完整的例子:

    #include <functional>
    #include <iostream>
    #include <tuple>
    #include <map>
    
    template <typename ReturnType, typename... Args>
    std::function<ReturnType (Args...)> memoize(std::function<ReturnType (Args...)> func) {
      std::map<std::tuple<Args...>, ReturnType> cache;
      return ([=](Args... args) mutable {
              std::tuple<Args...> t(args...);
              if (cache.find(t) == cache.end())
                 cache[t] = func(args...);
              return cache[t];
      });
    }
    
    std::function<int (int)> f;
    int fib(int n) {
      if  (n < 2) return n;
      return f(n-1) + f(n-2);
    }
    
    std::function<int (int, int)> b;
    int binomial(int n, int k) {
      if (k == 0 || n == k) return 1;
      return b(n-1, k) + b(n-1, k-1);
    }
    
    int main(void) {
      f = memoize(std::function<int (int)>(fib));
      std::cout << f(20) << std::endl;
      b = memoize(std::function<int (int, int)>(binomial));
      std::cout << b(34,15) << std::endl;
    }
    

    【讨论】:

    • @Felics 似乎要求解释代码 - 特别是缓存机制。你能帮忙吗?
    • slackito link已经给出了解释。
    【解决方案4】:

    引用您发现此内容的博客,就在代码下方:

    ...[=] 中的等号表示“捕获 周围的局部变量 按价值划分范围”,这是必需的 因为我们正在返回 lambda 函数,局部变量将 在那一刻消失。

    所以,cache 被复制到返回的函数对象中,就好像它是一个成员一样。

    (请注意,这段简单的代码将无法记住递归函数。在 C++0x 中实现 fixed-point combinator 留给读者作为练习。)

    【讨论】:

      【解决方案5】:

      欢迎来到词法作用域的美妙世界。它可用于创建具有公共和私有成员的整个类型。在函数式语言中,这通常是唯一的方法。

      我建议你阅读http://mark-story.com/posts/view/picking-up-javascript-closures-and-lexical-scoping,它是关于 Javascript 的,但 C++0x 为 C++ 添加了相同的概念和(几乎相同的)行为。

      【讨论】:

      • 虽然它是真实的,但它不是很有帮助;)
      • @delnan:SO 规则生效...先发帖,然后查找您的链接。
      • 正如在 Scheme 世界中所说的那样,“对象是穷人的闭包。”
      • @larsman:我对这条评论笑了,因为我只是在我的回答中用 OOP 术语解释了它!
      猜你喜欢
      • 2011-12-31
      • 1970-01-01
      • 1970-01-01
      • 2011-12-21
      • 2011-04-13
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      相关资源
      最近更新 更多