Last time, we looked at a way to use the RAII design pattern (Resource Acquisition Is Initialization) to ensure that a function eventually got called on all code paths, even if one of the “eventually” involves waiting for a callback. We solved the problem by using an RAII type which performs the cleanup action in its destructor and moving that type into the capture of the callback lambda.
This plan requires that the function that accepts the callback lambda never tries to copy the lambda, because our RAII type is not copyable. (If it were copyable, the one-time action will get executed twice, once when the original and destructs, and once when the copy destructs.) But what if the function requires a copyable lambda?
void MySpecialFeature::OnButtonClick()
{
auto ensureDismiss = wil::scope_exit([self = shared_from_this()]
{ self->DismissUI(); });
try {
auto folder = PickOutputFolder();
if (!folder) {
return;
}
if (ConfirmAction()) {
if (m_useAlgorithm1) {
// StartAlgorithm1 invokes the lambda when finished.
StartAlgorithm1(file,
[ensureDismiss = std::move(ensureDismiss)] { });
} else {
RunAlgorithm2(file);
}
}
} catch (...) {
}
}
Suppose that you get an error somewhere inside the call to StartAlgorithm1
because it tries to copy the non-copyable lambda. How can you make the lambda copyable while still getting the desired behavior of cleaning up exactly once, namely when the lambda is run?
Start by wrapping the RAII type inside a shared_ptr
:
// StartAlgorithm1 invokes the lambda when finished.
StartAlgorithm1(file, [ensureDismiss =
std::make_shared<decltype(ensureDismiss)>(move(ensureDismiss))]
{ ⟦ ... ⟧ });
There’s a bit of repetitiveness because shared_
does not infer the wrapped type if you are asking to make a shared_
by move-constructing from an existing object, so we have to write out the decltype(ensureDismiss)
.
Inside the body, we reset the RAII type, which calls the inner callable. Since all of the copies of the lambda share the same RAII object, the reset()
call performs the cleanup operation on behalf of everybody.
// StartAlgorithm1 invokes the lambda when finished.
StartAlgorithm1(file, [ensureDismiss =
std::make_shared<decltype(ensureDismiss)>(move(ensureDismiss))]
{ ensureDismiss->reset(); });
In the weird case that all of the copies of the lambda are destructed without any of them ever being called, then when the final one destructs, it will destruct the RAII object, which will run the cleanup operation if it hasn’t been done yet.
0 comments
Be the first to start the discussion.