Нужно ли вызывать destroy для std::coroutine_handle?

avatar
Brotcrunsher
12 июля 2021 в 19:05
481
1
16

std::coroutine_handle — важная часть новых сопрограмм C++20. Например, генераторы часто (всегда?) используют его. Дескриптор вручную уничтожается в деструкторе сопрограммы во всех примерах, что я видел:

struct Generator {
    // Other stuff...
    std::coroutine_handle<promise_type> ch;

    ~Generator() {
        if (ch) ch.destroy();
    }
}

Это действительно необходимо? Если да, то почему это уже не сделано с помощью coroutine_handle, существует ли RAII-версия coroutine_handle, которая ведет себя таким образом, и что произойдет, если мы опустим вызов destroy?

Примеры:

  1. https://en.cppreference.com/w/cpp/coroutine/coroutine_handle (Спасибо 463035818_is_not_a_number)
  2. Стандарт C++20 также упоминает об этом в 9.5.4.10 Пример 2 (проверено на N4892).
  3. (немецкий) https://www.heise.de/developer/artikel/Ein-unendlicher-Datenstrom-dank-Coroutinen-in-C-20-5991142.html
  4. https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html — Упоминает, что произошла бы утечка, если бы его не вызывали, но не цитирует отрывок из стандарта или почему он не вызывается в деструкторе std::coroutine_handle.
Источник
Fureeish
12 июля 2021 в 19:07
0

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

Brotcrunsher
12 июля 2021 в 19:10
0

@Fureeish Напрашивается вопрос, какие примеры были лучше.

Brotcrunsher
12 июля 2021 в 19:18
0

@ 463035818_is_not_a_number На самом деле даже стандарт C ++ 20 (я смотрел на n4892, но я думаю, что реальный выпуск имеет аналогичную формулировку) включает его. См. 9.5.4.10.

463035818_is_not_a_number
12 июля 2021 в 19:19
1

я нашел пример здесь en.cppreference.com/w/cpp/coroutine/coroutine_handle. Тем не менее, я думаю, вы должны включить ссылку на пример в свой вопрос, чтобы другие знали, о чем вы говорите.

Mgetz
12 июля 2021 в 19:28
0

Основываясь на моем опыте работы с CPPwinrt... это зависит от обстоятельств. Если вы используете генератор, вам нужно вызвать destroy. Если нет, то нет. Но я бы не стал считать это авторитетным. Изменить cppcoro содержит несколько примеров того, когда это уместно.

Ответы (1)

avatar
Hatted Rooster
12 июля 2021 в 19:37
10

Это потому, что вы хотите, чтобы сопрограмма переживала свой дескриптор, дескриптор должен быть невладельцем. Дескриптор — это просто «представление», очень похожее на std::string_view -> std::string. Вы бы не хотели, чтобы std::string уничтожал себя, если std::string_view выходит за рамки.

Однако, если вам нужно такое поведение, создание собственной оболочки вокруг него будет тривиальной задачей.

При этом стандарт предписывает:

Состояние сопрограммы уничтожается, когда управление переходит за конец сопрограмма или функция-член destroy ([coroutine.handle.resumption]) дескриптора сопрограммы Вызывается ([coroutine.handle]), который ссылается на сопрограмму.

Состояние сопрограммы очистится после того, как завершится ее выполнение, и, таким образом, не произойдет утечка, если только управление не перейдет с конца.

Конечно, в случае с генератором управление обычно не происходит с конца, и поэтому программисту приходится уничтожать сопрограмму вручную. Однако сопрограммы имеют несколько применений, и поэтому стандарт не может действительно безоговорочно предписывать вызов деструктора дескриптора destroy().

.
Brotcrunsher
12 июля 2021 в 19:40
1

Если я правильно понял, потоки управления генератором обычно не заканчиваются сами по себе, поскольку они часто имеют бесконечные циклы внутри.

Hatted Rooster
12 июля 2021 в 19:45
0

@Brotcrunsher Для случая генератора обычно да, но это не единственное использование сопрограмм, и поэтому вы не можете безоговорочно вызывать destroy() для деструктора дескриптора из стандартного PoV.

Brotcrunsher
12 июля 2021 в 20:02
0

Довольно близко к принятию этого ответа. Единственное, чего не хватает, так это цитаты из стандарта, в которой говорится, что произойдет, если мы не сможем вызвать destroy. Вероятно, это утечка, но может быть и UB.

Hatted Rooster
12 июля 2021 в 20:07
0

@Brotcrunsher Насколько мне известно, стандарт мало что говорит об этом. Это только дает нам встречную ситуацию вызова destroy для сопрограммы, которая не приостановлена, это UB. Я считаю, что отсутствие вызова destroy() означало бы, что у вас есть состояние сопрограммы, которое живет где-то в памяти, к которому вы больше не можете получить доступ или уничтожить, что приводит к утечке. все равно что не звонить delete на new.