Когда вы пишете
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
, реализации должны отложить вызов или выбрать политику, когда не будет эффективно использоваться. -End note]
Итак, у вас есть гарантия, что расчет не будет вызываться до того, как он понадобится.
фьючерсы ждут результата какого-то (возможно асинхронного) процесса. Они одноразовые и довольно тяжеловесные. Если вы ищете ленивую оценку в той же теме, возможно, не то, что вам нужно. Существует библиотека под названием boost.outcome, которая разрабатывается. Это, по сути, легкие фьючерсы (не предназначенные для кросс-ниточной работы). Если вы хотите многократно называть свою ленивую функцию, возможно, подходящим является объект функции или лямбда. Вы также можете посмотреть на boost.hana или подобное. –