Я просто читал:
и заметил, что он довольно старый, и большинство ответов касаются C ++ до 2011 года. В наши дни у нас есть синтаксические лямбды, которые могут даже определить тип возвращаемого значения, поэтому ленивое вычисление сводится к тому, чтобы просто передать их: вместо
auto x = foo();
вы выполняете
auto unevaluted_x = []() { return foo(); };
а затем оцените, когда / где вам нужно:
auto x = unevaluted_x();
Кажется, больше ничего нет. Тем не менее, один из ответы там предлагает использовать фьючерсы с асинхронным запуском. Может кто-нибудь выложить, почему / если фьючерсы важны для ленивых вычислений, в C ++ или более абстрактно? Кажется, что фьючерсы могут быть очень хорошо оценены с нетерпением, но просто, скажем, в другом потоке, и, возможно, с меньшим приоритетом, чем те, которые их создали; и вообще, это должно зависеть от реализации, верно?
Кроме того, существуют ли другие современные конструкции C ++, о которых полезно помнить в контексте отложенной оценки?
Когда ты пишешь
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
реализации должны отложить вызов или выбор политики, когда
больше параллелизм не может быть эффективно использован. —Конечная записка]
Таким образом, у вас есть гарантия, что расчет не будет вызван, пока он не понадобится.
Здесь происходит несколько вещей.
Applicative order
оценка означает оценку аргументов перед передачей их в функцию.
Normal order
оценка означает передачу аргументов в функцию перед их оценкой.
Преимущество обычной оценки порядка состоит в том, что некоторые аргументы никогда не оцениваются, и недостатком является то, что некоторые аргументы оцениваются снова и снова.
Lazy
оценка обычно означает normal order + memoization
, Отложите оценку в надежде, что вам вообще не нужно оценивать, но если вам нужно, запомните результат, так что вам придется делать это только один раз. Важной частью является оценка термина «никогда», «памятка» — самый простой механизм для этого.
promise/future
модель снова другая. Идея в том, чтобы начать оценку, возможно, в другой ветке, как только у вас будет достаточно информации. Затем вы продолжаете смотреть на результат как можно дольше, чтобы повысить вероятность того, что он уже доступен.
promise/future
Модель обладает некоторой интересной синергией с ленивой оценкой. Стратегия идет:
Мемоизация может быть аккуратно введена, когда результат создается фоновым потоком.
Несмотря на синергию между ними, это не одно и то же.