I decided to start up a new Redpoint Games blog as I wanted somewhere to put all the esoteric and weird Unreal Engine knowledge that I’ve accumulated over the past year.
Without further ado, here is how to make circularly referenced lambdas in C++; that is, lambdas that reference each other or themselves.
When writing test code for asynchronous behaviour, I’d frequently run into a problem where lambdas are dependent on each other, like so:
This obviously doesn’t work. We can’t shift the FDelegateHandle
declarations above Cleanup
, because the delegate handle is on the stack, and when the clean up lambda gets created, it’ll capture the current value of the delegate handle. Even if we later assign to CallbackHandle
, that won’t get propagated into Cleanup
.
At the same time, the lambda we’re passing into AddEventListener
needs to be able to reference Cleanup
so that the test ends and passes immediately when the event listener fires. We could workaround the problem in this particular scenario by only cleaning up in the timer, but not only does this make the test slower, it’s not applicable to all cases with circularly dependent lambdas.
For trivial cases where we need a lambda to reference itself, we can create a double pointer to the lambda. We pass that pointer into the lambda, and after we’ve declared the lambda, we then move it onto the heap with std::move
.
With the lambda referencing itself, this enables the lambda to re-invoke itself at some later point. This is useful if you need to perform retries or exponential back-off with some service, and you want to allow the developer to just pass in an std::function
or lambda and call it a day.
This solution only works if you’re cleaning up the lambda in exactly one place, and you can guarantee the code performing the clean up will only ever be called once.
For the problem we started with, we’ll need a solution that’s a little more involved. We’ll need to put our lambdas on the heap and reference count them.
Introducing FHeapLambda
. This class allows you to create a “to be filled in” reference to a lambda; you can let this value be captured by other lambdas, and when it’s finally assigned, all of the copies of the FHeapLambda
will point at the same place.
This is done by allocating a state object on the heap when FHeapLambda
is first constructed (with the default constructor). Every copy and move increments the reference counter; while the destructor decrements the counter. When the reference counter reaches 0
in any of the copies, it knows it’s the last possible reference to the state and thus the heap-allocated state can be freed.
When you assign a lambda to an FHeapLambda
, the lambda gets copied into that heap allocated state.
Since we’ll often be using FHeapLambda
with clean up actions and delete
statements, the implementation below optionally supports both “always fire at least once” and “fire at most once” semantics. Combined together you can guarantee that your clean up code runs regardless of the circumstances that causes the lambda to no longer be referenced.
FHeapLambda
implementation below has been modified to fix unsafe usage of this
in the invocation handler and to support Clang (Android), since this blog post was originally published.
Now we can use FHeapLambda
to solve our original problem quite nicely.