Ответ 1
Когда вы пишете
auto unevaluted_x = []() { return foo(); };
...
auto x = unevaluted_x();
Каждый раз, когда вы хотите получить значение (когда вы вызываете unevaluated_x
), он рассчитывается, теряя вычислительные ресурсы. Таким образом, чтобы избавиться от этой чрезмерной работы, неплохо было бы отслеживать, была ли уже запрошена лямбда (может быть, в другом потоке или в другом месте в кодовой базе). Для этого нам нужна обертка вокруг лямбда:
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;
};
Обратите внимание, что этот код является лишь примером и не является безопасным для потоков.
Но вместо того, чтобы изобретать колесо, вы можете просто использовать std::shared_future
:
auto x = std::async(std::launch::deferred, []() { return foo(); }).share();
Для этого требуется меньше кода для записи и поддержки некоторых других функций (например, проверьте, уже ли рассчитано значение, безопасность потоков и т.д.).
В стандарте [futures.async, (3.2)] имеется следующий текст:
Если
launch::deferred
задано в политике, сохраняетDECAY_COPY(std::forward<F>(f))
иDECAY_COPY(std::forward<Args>(args))...
в общем состоянии. Эти копииf
иargs
составляют отложенная функция. Вызов отложенной функции оценивает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
, реализации должны отложить вызов или выбрать политику, когда не более concurrency можно эффективно использовать. -end note]
Итак, у вас есть гарантия, что расчет не будет вызываться до того, как он понадобится.