Ленивая оценка в C ++ 14/17 — просто лямбды или фьючерсы и т. Д.?

Я просто читал:

Ленивая оценка в C ++

и заметил, что он довольно старый, и большинство ответов касаются C ++ до 2011 года. В наши дни у нас есть синтаксические лямбды, которые могут даже определить тип возвращаемого значения, поэтому ленивое вычисление сводится к тому, чтобы просто передать их: вместо

auto x = foo();

вы выполняете

auto unevaluted_x = []() { return foo(); };

а затем оцените, когда / где вам нужно:

auto x = unevaluted_x();

Кажется, больше ничего нет. Тем не менее, один из ответы там предлагает использовать фьючерсы с асинхронным запуском. Может кто-нибудь выложить, почему / если фьючерсы важны для ленивых вычислений, в C ++ или более абстрактно? Кажется, что фьючерсы могут быть очень хорошо оценены с нетерпением, но просто, скажем, в другом потоке, и, возможно, с меньшим приоритетом, чем те, которые их создали; и вообще, это должно зависеть от реализации, верно?

Кроме того, существуют ли другие современные конструкции C ++, о которых полезно помнить в контексте отложенной оценки?

13

Решение

Когда ты пишешь

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реализации должны отложить вызов или выбор политики, когда
больше параллелизм не может быть эффективно использован. —Конечная записка]

Таким образом, у вас есть гарантия, что расчет не будет вызван, пока он не понадобится.

13

Другие решения

Здесь происходит несколько вещей.

Applicative order оценка означает оценку аргументов перед передачей их в функцию.
Normal order оценка означает передачу аргументов в функцию перед их оценкой.

Преимущество обычной оценки порядка состоит в том, что некоторые аргументы никогда не оцениваются, и недостатком является то, что некоторые аргументы оцениваются снова и снова.

Lazy оценка обычно означает normal order + memoization, Отложите оценку в надежде, что вам вообще не нужно оценивать, но если вам нужно, запомните результат, так что вам придется делать это только один раз. Важной частью является оценка термина «никогда», «памятка» — самый простой механизм для этого.

promise/future модель снова другая. Идея в том, чтобы начать оценку, возможно, в другой ветке, как только у вас будет достаточно информации. Затем вы продолжаете смотреть на результат как можно дольше, чтобы повысить вероятность того, что он уже доступен.


promise/future Модель обладает некоторой интересной синергией с ленивой оценкой. Стратегия идет:

  1. Отложите оценку, пока результат точно не понадобится
  2. Начать оценку можно в другой ветке
  3. Сделать что-то еще
  4. Фоновый поток завершает и сохраняет результат где-то
  5. Исходный поток извлекает результат

Мемоизация может быть аккуратно введена, когда результат создается фоновым потоком.

Несмотря на синергию между ними, это не одно и то же.

4

По вопросам рекламы [email protected]