这个C ++ 11代码(memoize)做了什么?

时间:2011-03-18 14:07:08

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

我发现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];
    });
}

你能解释一下吗?我在这里无法理解很多东西,但最奇怪的是缓存是本地的而不是静态的,但也许我错了......

5 个答案:

答案 0 :(得分:25)

这是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];
    }
};

答案 1 :(得分:9)

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

因此,如果你创建了两个lambda,每个lambda都有一个自己的缓存。

这是实现简单缓存的好方法,因为这样一旦lambda超出范围就会清除所使用的内存,并且你不会爆发内存。

答案 2 :(得分:3)

&#34;这段简单的代码&#34;如果正确调用它,也可以记住递归函数。在这里,我举一个完整的例子:

#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;
}

答案 3 :(得分:2)

引用你发现这个的博客,就在代码下面:

  

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

因此,cache被复制到返回的函数对象中,就像它是成员一样。

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

答案 4 :(得分:0)

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

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