Сохраняет ли std::future лямбду std::async после выполнения?

Для этого примера:

#include <vector>
#include <future>
#include <memory>
struct S {
  std::string mName = "";
};

std::vector<std::future<void>> futures;
int main(){
  {
    auto s = std::make_shared<S>();
    futures.emplace_back(std::async([s2=s]{}));
  }
  // will s2 be alive here assuming the async execution is done?
}

сохранит ли сохраненное будущее лямбду в живых или эта лямбда и ее захваты по значению будут уничтожены после завершения асинхронного выполнения?

s выходит за рамки, в закомментированной строке он не может быть живым.
3CxEZiVlQ 30.07.2024 19:08

@3CxEZiVlQ Они спрашивают о s, которое представляет собой лямбду, хранящуюся в глобальном объекте будущего. Не объект области видимости блока s.

NathanOliver 30.07.2024 19:10

Было бы странно, если бы в будущем объект функции был уничтожен раньше, чем его собственный. Я ожидаю, что функция будет частью общего состояния и не будет уничтожена до тех пор, пока не будет последней ссылки.

NathanOliver 30.07.2024 19:10
Кажется, ответ — да
LHLaurini 30.07.2024 19:18

Я не думаю, что в Стандарте об этом что-то сказано, и, кроме побочного эффекта в ~S, нет никакого способа использовать это. Итак, как вы собираетесь использовать это на практике? И что значит после казни? -- когда будущее станет готовым?

Gene 30.07.2024 19:21

gcc, по крайней мере, не уничтожает захват, пока вы не получите результат из будущего: godbolt.org/z/chq4cqraK

Gene 30.07.2024 19:36

std::futures, возвращаемые std::async, блокируются. Таким образом, разрушение фьючерсов будет синхронизировано с завершением ступеней. И лямбда будет жива до тех пор, пока она нужна потоку, поэтому ее захват по переменным значения тоже будет активен. (Так что на самом деле фьючерсы без std::jthread — это способ синхронизации с потоком в стиле RAII)

Pepijn Kramer 30.07.2024 19:37

@3CxEZiVlQ Отредактирован вопрос, теперь захват называется s2, что должно прояснить ситуацию.

Yakk - Adam Nevraumont 30.07.2024 20:02

@NathanOliver Функциональный объект в асинхронном режиме можно вызвать только один раз. Имеет смысл очистить его, как только он будет закончен; зачем хранить ресурсы, которые гарантированно никогда больше не будут использованы? Если бы я писал спецификацию для std Future, я бы заставил ее уничтожить вызываемый объект после его завершения и просто сохранить возвращаемое значение (или исключение). То есть, если бы я вообще не забыл это указать!

Yakk - Adam Nevraumont 30.07.2024 20:03

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

Yakk - Adam Nevraumont 30.07.2024 20:04

@Yakk - Адам Невромонт - вопрос сформулирован неправильно. Есть две точки, в которых будущее может избавиться от callable. Во-первых, когда общее состояние становится готовым, и во-вторых, когда значение извлекается из общего состояния. Компиляторы делают последнее, хотя ничто не мешает им сделать первое: godbolt.org/z/Gjxo9x6hz

Gene 30.07.2024 20:13

@Gene Если вы добавите в свой код общее будущее (вот так), время жизни вызываемого объекта теперь продлится .get() ... на самом деле без веской причины. Я имею в виду, что причина в том, что внутренний объект общего состояния с возвращаемым значением также содержит вызываемый объект, но на самом деле у этих двух нет причин разделять время жизни.

Yakk - Adam Nevraumont 30.07.2024 20:18

@Yakk-AdamNevraumont Меня действительно беспокоит то, что говорит стандарт, или, по крайней мере, gcc и msvc, я просмотрел стандарт и ничего не нашел, но не уверен, что я что-то пропустил.

muaz 30.07.2024 20:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
13
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Объявление std::async, которое вы используете, соответствует [futures.async]:

template<class F, class... Args>
  [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
    async(F&& f, Args&&... args);

Прежде всего, исходный лямбда-объект, переданный для f, является временным объектом, который будет уничтожен в конце полного выражения.

futures.emplace_back(std::async([s2=s]{}))

и его захват s2 вместе с ним. Однако std::async переместится из лямбды в реальный объект, который будет вызван. Эта копия построена как будто с помощью auto(std::forward<F>(f)) (см. ниже). Вероятно, ваш вопрос заключается в том, когда этот объект будет уничтожен, забрав с собой последнюю ссылку на объект S.

Если вы не задаете аргумент политики запуска для std::async, по умолчанию используется std::launch::async | std::launch::deferred, что означает, что реализация может выбрать любую из двух политик. См. [futures.async]/3.

Если реализация выбирает std::launch::async, то она будет вести себя так, как если бы запускался поток, который выполняется ([futures.async]/3.1):

invoke(auto(std​::​forward<F>(f)), auto(std​::​forward<Args>(args))...)

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

Если реализация выбирает std::launch::deferred, то она должна хранить копию g из f, инициализированную auto(std​::​forward<F>(f)), в общем состоянии std::future, и аналогичным образом копии xyz... из args..., инициализированные auto(std::forward<Args>(args)).... Когда вызывается отложенная функция, она должна выполнить

invoke(std​::​move(g), std​::​move(xyz)...)

См. [futures.async]/3.2.

Здесь уничтожение копии f не является частью выражения или отложенным вызовом функции.

[futures.async]/3.2 далее не уточняется, когда эти объекты будут уничтожены.

Я думаю, учитывая, что [futures.async]/3.2 четко указывает, когда и как копия f хранится в общем состоянии, и не говорит, что она заменяется или высвобождается, реализация не должна просто уничтожать ее. потому что он больше не нужен после завершения отложенного вызова.

std::future::wait также не указывает, что он высвобождает общее состояние будущего, поэтому он сам также не должен разрушать g.

С другой стороны, std::future::get предназначен для освобождения всего общего состояния и поэтому должен уничтожить g.

Перемещение std::future в futures также переместит состояние g, создав еще одну копию лямбды, но снова перенеся владение объектом S на s2 в новую копию.

Итак, для вашего конкретного примера, я думаю, неизвестно, будет ли объект S еще жив в закомментированной строке. Это зависит от того, какую политику выберет реализация: если она выберет launch::async, то она будет уничтожена, точнее, ее уничтожение произойдет в другом потоке несинхронно с достижением строки комментария. Если он выберет launch::deferred, то он не будет уничтожен (поскольку вы не вызвали ни одну функцию-член std::future, которая освободит его состояние).

Другие вопросы по теме

Как я могу использовать лямбда-выражение в качестве постоянного основного выражения в предложении require?
C# LinQ: фильтровать объект[] с элементами, содержащими строку (или ее часть) из строки[]
Неточные вычисления последовательности Фибоначчи во время компиляции в рекурсивной лямбда-выражении
Ясность синтаксиса Java Lambda
В GCC внутри лямбды я могу получить переменную constexpr из лямбды шаблона, отличного от constexpr, но не в Visual C++
Получить переменную constexpr из лямбда-функции можно, но компиляция завершается неудачей (Visual C++) и штрафом (gcc), когда такой оператор находится в новой лямбда-функции
Каков тип шаблонной лямбды?
Тернарный оператор не работает внутри функции Lambda Ruby
Создание универсального селектора/установщика свойств на C#, если вы знаете тип только во время выполнения
Как преобразовать лямбда-функцию в другую, используя кортеж параметров