Что делает этот код С++ 11 (memoize)?

Я нашел статью, которая содержит этот код:

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

Можете ли вы это объяснить? Я не могу понять много вещей здесь, но самое странное, что кеш является локальным, а не статическим, но, возможно, я ошибаюсь и...

Ответы

Ответ 1

Это простая реализация С++ 1x memoization.

Функция memoize возвращает closure. Возвращаемое значение - это функция, которая имеет состояние, отличное от того, что передается через аргументы (в данном случае переменная cache).

Бит [=] в анонимной функции указывает, что возвращаемая функция должна взять копию всех локальных переменных. Переменная cache не является статической, поскольку она предназначена для совместного использования между вызовами возвращаемой функции.

Таким образом, каждый вызов memoize возвращает другую функцию с собственным cache. Последующие вызовы к конкретному закрытию, возвращаемые memoize, будут вставлять/извлекать значения из этого закрытия cache.

Вы можете думать об этом как о чем-то эквивалентном более старой версии ООП:

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

Ответ 2

Кэш встроен в сам лямбда и локален для него.

Поэтому, если вы создадите два лямбда, у каждого будет свой кеш.

Это отличный способ реализовать простой кеш, так как эта память используется, как только лямбда выходит из области видимости, и у вас нет взрыва памяти.

Ответ 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;
}

Ответ 4

Процитировать из блога, где вы нашли это, чуть ниже кода:

... знак равенства в [=] означает "захват локальные переменные в окружающем масштаб по значению", который необходим потому что мы возвращаем лямбда функция, а локальная переменная будет исчезают в этот момент.

Итак, cache копируется в возвращаемый объект функции, как если бы он был членом.

(Обратите внимание, что эта простая часть кода не сможет воспроизвести рекурсивную функцию. Внедрение компилятора с фиксированной запятой в С++ 0x оставил упражнение для читателя.)

Ответ 5

Добро пожаловать в замечательный мир лексического охвата. Его можно использовать для создания целых типов с участием публичных и частных членов. В функциональных языках это часто единственный способ сделать это.

Я предлагаю вам читать http://mark-story.com/posts/view/picking-up-javascript-closures-and-lexical-scoping, что касается Javascript, но С++ 0x добавляет те же концепции и (почти то же самое) поведение на С++.