Last Update:
Beautiful code: final_act from GSL
Table of Contents
Sometimes there’s a need to invoke a special action at the end of the
scope: it could be a resource releasing code, flag set, code guard,
begin/end function calls, etc. Recently, I’ve found a beautiful utility
that helps in that cases.
Let’s meet gsl::final_act
/finally
.
Intro
Follow-up post here: link.
Imagine we have the following code:
void addExtraNodes();
void removeExtraNodes();
bool Scanner::scanNodes()
{
// code...
addExtraNodes();
// code...
removeExtraNodes();
return true;
}
We have a bunch of objects that scanNodes
scans (global or shared
container), but then we need to add some extra nodes to check. We want
to preserve the initial container state, so at the end, we’re required
to remove those additional nodes.
Of course, the design of the whole scan code could be much better so that we work on a copy of the container and adding or removing extra stuff would not be a problem. But there are places, especially in legacy code, where you work on some global container, and special care needs to be taken when changing it. A lot of bugs can happen when you modify a state, and someone expects a different state of the shared container.
My code seems to be working as expected… right? I call
removeExtraNodes
at the end of the function.
But what if there are multiple returns from scanNodes
? It’s simple: we
need to add multiple calls to removeExtraNodes
. Ok….
What if there are some exceptions thrown? Then we also need to call our cleanup function before we throw…
So it appears we need to call removeExtraNodes
not only before the
last return!
Help needed
Let’s look at the C++ Core Guidelines. They suggest doing the following thing:
E.19: Use a final_action object to express cleanup if no suitable resource handle is available
The guideline says that we should strive for a better design, but still, it’s better than goto; exit approach, or doing nothing.
Ok… but what’s the solution here:
bool Scanner::scanNodes()
{
// code...
addExtraNodes();
auto _ = finally([] { removeExtraNodes(); });
// code...
return true;
}
What happened here?
All I did was to wrap the call to removeExtraNodes
in a special object
that will call a given callable object in its destructor. This is
exactly what we need!
Where can we find that magical finally()
code?
Just see Guideline Support Library/gsl_util.h.
Under the hood
The code is short, so I can even paste it here:
template <class F>
class final_act
{
public:
explicit final_act(F f) noexcept
: f_(std::move(f)), invoke_(true) {}
final_act(final_act&& other) noexcept
: f_(std::move(other.f_)),
invoke_(other.invoke_)
{
other.invoke_ = false;
}
final_act(const final_act&) = delete;
final_act& operator=(const final_act&) = delete;
~final_act() noexcept
{
if (invoke_) f_();
}
private:
F f_;
bool invoke_;
};
Isn’t that beautiful?!
The above class takes a callable object - f_
- and then it will call
it when it’s about to be destroyed. So even if your code returns early
or throws an exception your cleanup code is required to be invoked.
To work nicely with move semantics, there has to be an additional
boolean parameter invoke_
. This will guarantee that we won’t call the
code for temporary objects. See this commit for more information if
needed:
Final_act copy/move semantics is
wrong.
Additionally, to make our life easier, we have function helpers that create the objects:
template <class F>
inline final_act<F> finally(const F& f) noexcept
{
return final_act<F>(f);
}
template <class F>
inline final_act<F> finally(F&& f) noexcept
{
return final_act<F>(std::forward<F>(f));
}
So all in all, we can use finally()
function in the client code. Maybe
that could change in C++17 as we’ll get Template argument deduction for
class
templates.
What’s nice about this code?
- Clean, simple code
- Expressive, no comments needed
- Does one thing only
- It’s generic, so works on anything that’s callable
- Modern C++: so supports move semantics, noexcept,
Where could be used?
Just to be clear: don’t use finally
approach too often! With the
proper design, your objects shouldn’t work on a global state and take
benefit from RAII as much as possible. Still, there are situations where
finally
is nice to use:
- begin/end functions - where you’re required to call
end
after something started. As in our example. - flag setters. You have a shared flag, and you set it to a new state, but you have to reset it to the old state when you’re done.
- resources without RAII support. The guideline shows an example with
malloc/free. If you cannot wrap it in an RAII object (for example by
using smart pointers and custom
deleters),
final_act
might work. - safely closing the connection - as another example for resource clean-up in fact.
Do you see other places where final_act
can help?
You can also look at this list: C++ List of ScopeGuard that appeared some time on Reddit (thread here)
Summary
Follow-up post here: link.
final_act
/finally
is a beautiful and well-designed tool that can
help with a dirty job of cleaning stuff. In your code, you should go for
a better approach to clean things/resources, but if that’s not possible
final_act
is a great solution.
Do you use similar classes to clean things in your code?
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: