qcoro
qcoro copied to clipboard
Add lifetime docs for possible footguns
QCoro is an amazing library that I heavily rely on (ScreenPlay), but I would like to propose some sort of known pitfalls like I encountered when using emits after co_await:
QCoro::QmlTask CppBackend::test()
{
// ⚠️ This will crash:
// This crashes because the lambda captures this by value,
// but the lifetime of the coroutine extends beyond the function
// call. When longFunction() is co_awaited, the coroutine is
// suspended, and control returns to the caller.
// The QmlTask object is then returned, but the lambda
// (and thus the captured this) goes out of scope. When the coroutine
// resumes later, it tries to use the now-invalid this pointer, causing a crash.
return QCoro::QmlTask([this]() -> QCoro::Task<Result> {
co_await longFunction();
emit testCallSignal();
co_return Result{};
}());
// ✅ This will _not_ crash
// This doesn't crash because the emit testCallSignal() is executed within
// the then() callback, which is called immediately after longFunction() completes.
// The this pointer is still valid at this point.
return QCoro::QmlTask([this]() -> QCoro::Task<Result> {
co_await longFunction().then([this]() { emit testCallSignal(); });
co_return Result{};
}());
// ✅ This will _not_ crash
// This example also doesn't crash for the same reason as the previous one.
// All emit testCallSignal() calls are within then() callbacks, which are executed
// immediately after their respective longFunction() calls complete.
return QCoro::QmlTask([this]() -> QCoro::Task<Result> {
co_await longFunction().then([this]() -> QCoro::Task<Result> {
emit testCallSignal();
co_await longFunction().then([this]() { emit testCallSignal(); });
co_return Result{};
});
co_return Result{};
}());
// ⚠️ This will crash:
// This case crashes for the same reason as the first crashing example.
// Although each longFunction().then() call is safe, the coroutine is suspended
// between these calls. The this pointer captured by the outer lambda may become
// invalid during these suspensions, leading to a crash when the coroutine resumes.
return QCoro::QmlTask([this]() -> QCoro::Task<Result> {
co_await longFunction().then([this]() { emit testCallSignal(); });
co_await longFunction().then([this]() { emit testCallSignal(); });
co_await longFunction().then([this]() { emit testCallSignal(); });
co_return Result{};
}());
}
You can copy this into the docs if you like :)
Hi, thanks for the feedback, you are right the docs are not very good in this regard.
Thanks for the examples, I'll write something down and use your examples.