Почему параметры корутины обычно следует передавать по значению?
Изучите корутину ниже. Вызывающая сторона передаёт временную строку, корутина приостанавливается на co_await, а позже использует параметр s.
Task<void> process(const std::string& s) {
co_await something(); // корутина приостанавливается здесь
use(s); // валиден ли ещё s?
}
void caller() {
process(std::string{"temp"});
}
Объясните, безопасен ли use(s), что определяет время жизни параметра относительно coroutine frame и что изменится, если объявить s по значению (std::string s) вместо ссылки. Укажите, какая форма корректна и почему.
Параметр по значению копируется в coroutine frame, поэтому он живёт через приостановки всё время жизни корутины. Ссылочный параметр лишь указывает на объект вызывающей стороны — если она вернётся раньше, ссылка становится висячей, что является undefined behavior.
- ✗Думать, что ссылочный параметр копируется в фрейм, как параметр по значению
- ✗Считать, что стековый фрейм вызывающей стороны живёт до завершения корутины
- ✗Полагать, что компилятор автоматически отвергает или переписывает ссылочные параметры
- →Безопасна ли захватывающая лямбда, используемая как корутина, через приостановки?
- →Когда ссылочный параметр в корутине всё же приемлем?
Опасно: ссылочный параметр
Task<void> danger(const std::string& s) { // s — ссылка на объект вызывающей стороны
co_await something(); // корутина приостановлена; кадр danger() ушёл
use(s); // UB: s может уже не существовать
}
void caller() {
danger(std::string{"temp"}); // временная строка живёт до конца выражения
} // ...а корутина возобновится позже
В coroutine frame сохраняется сама ссылка (адрес), а не строка. Объект-строка остаётся на стеке или во временном хранилище вызывающей стороны. После первой приостановки caller успевает вернуться, временная строка уничтожается — ссылка s становится висячей.
Безопасно: параметр по значению
Task<void> safe(std::string s) { // s КОПИРУЕТСЯ в coroutine frame
co_await something(); // копия живёт вместе с фреймом
use(s); // OK: s валидна всё время жизни корутины
}
При передаче по значению копия параметра становится частью coroutine frame. Фрейм живёт, пока корутина не уничтожена, поэтому параметр переживает любые приостановки независимо от того, когда вернулась вызывающая сторона.