Some follow-up notes on
Emulating the C#
using keyword in C++,
primarily for the benefit of
people from reddit who stumbled into the series and
didn't understand the context of the discussion,
because this was really part 6 of the series begun the previous week,
even though it wasn't labeled as such.
(And the title itself was a party trick rather than a serious
The main complication that prevented us from using RAII was the use of the Parallel Patterns Library (PPL) to express asynchronous programming in C++.¹ The most general pattern for asynchronous programming is that you start an operation, and then specify a callback to be invoked when the operation completes. In traditional C-style programming, this callback is a boring function pointer, coupled with some reference data so the callback has context for why it is being called back. C++ provides lambdas which let you express the continuation as a callback object, which is much more convenient since you can express the control flow inline instead of having tiny pieces of control flow scattered all over your program. And lambda capture makes it easy to express what pieces of information needs to be carried forward to the continuation.
If we didn't have any asynchronous operations, then a basic RAII
class would do the trick:
When the RAII class destructs, the cleanup operation occurs.
(In our example, the cleanup operation is calling
The difficulty in the asynchonous case
is that it is cumbersome to keep carrying
this RAII class forward and preventing it
from destructing until the entire chain of continuations has completed.
That's where the
close classes entered the picture.
But you still had to remember to carry them forward.
The magical step was the
introduction of the not-yet-standard-but-hopefully-soon
transforms the function into a state machine,
the end of a state.
The current execution state is saved, and execution of the task
When the awaited operation completes, the execution state is
restored and the function resumes execution.
This transformation is very tedious and error-prone to perform
by hand (especially when there are loops and branches),
and in particular,
it preserves RAII semantics:
The automatic variables created by the pre-transformed function
become part of the execution state,
and they are destructed at the "natural" time they would have
been destructed prior to transformation.
As a result, switching to
co_await brings us full circle
back to plain old RAII.
Behind the scenes, the compiler is doing the wacky transformations
that we tried to mimic with
co_await lets us write the code in a far more natural way.
its lack of composability
makes it a poor choice for asynchronous programming.