Comments
You can use your Mastodon account to reply to this post.
Modern C++ has many neat RAII-based solutions for managing resources. However, every now and then one needs to manually make sure that some code is run when a resource is not needed anymore. One great solution to this problem is a scope guard. The concept is already very old, however I have surprisingly rarely seen them used (and used them too rarely myself). This article describes the concept, shows how to use it and gives some background on scope guards.
I’ll start by explaining the problem (essentially: manual resource management sucks). If you’re already convinced and just want to know how scope guards work to solve the problem, you can skip ahead to the second section. The final section contains an overview over available implementations.
In modern C++, whenever one uses resources (memory, file handles, sockets, …), it is good practice
to use wrapper classes around these resources that manage the lifetime of the resource following the
Resource Acquisition Is Initialization (RAII) idiom. This ties the resource management itself
(e.g. allocation and deallocation) to the lifetime of the wrapper object and prevents resource
leaks. The most famous examples of this technique are probably std::shared_ptr
and
std::unique_ptr
.
However, sometimes one needs to work directly with “raw” resources. This may be because you are working directly with e.g. network sockets, or because you are working with an old “C-style” library that requires manual initialization and deinitialization of its structures. If you pair this with the usual structure of “set-up functions”, which need to perform a lot of set-up steps, each of which can fail, this often results in ugly code like this:
1bool doSomething() {
2 MyFirstResource * first = acquireFirst();
3 if (!first) return false;
4
5 MySecondResource second;
6 initializeSecond(&second);
7 if (!isValid(&second)) {
8 delete first;
9 return false;
10 }
11
12 MyThirdResource third;
13 initializeThird(third);
14 if (!third.isValid) {
15 delete first;
16 freeSecond(&second);
17 return false;
18 }
19
20 // … do something useful with all the allocated resources …
21
22 // If we didn't pass ownership of the resources somewhere,
23 // clean them up!
24 delete first;
25 freeSecond(&second);
26 freeThird(third);
27
28 return true;
29}
This is very error prone. If you forget to free any of the previously allocated structures in one of
the “still everything okay?” check blocks, you are leaking resources in the case of an error. Also,
what happens if an exception is thrown after resources were allocated? You need a try
/ catch
block to avoid resource leaks. But is one try
/ catch
enough? How do you know whether you need to
call freeSecond
? Maybe the exception was thrown from initializeSecond
?
This is a mess, let’s see if we can do better.
Wouldn’t it be great if, in the above example, freeSecond
would automatically be called when the
lifetime of the second
object ends? It’s not that easy since we can not modify MySecondResource
,
but we can create a “helper” object and tie the deallocation of second
to the lifetime of that
helper object. This helper object is called a “scope guard”, since it guards second
with its (the
helper’s) own scope.
A very basic scope guard class could look as simple as this:
1template<class Func>
2class ScopeGuard {
3public:
4 ScopeGuard(Func f) : storedFunc(f) {}
5 ~ScopeGuard() { storedFunc(); }
6
7private:
8 Func storedFunc;
9};
It does nothing but take a callable in its constructor, store it, and call that callable when it is destroyed. With this, we can rewrite the example from above as:
1bool doSomething() {
2 MyFirstResource *first = acquireFirst();
3 if (!first)
4 return false;
5 auto firstGuard = ScopeGuard([&first]() { delete first; });
6
7 MySecondResource second;
8 initializeSecond(&second);
9 if (!isValid(&second))
10 return false;
11 auto secondGuard = ScopeGuard([&second]() { freeSecond(second); });
12
13 MyThirdResource third;
14 initializeThird(third);
15 auto thirdGuard = ScopeGuard([&third]() { freeThird(third); });
16 if (!third.isValid)
17 return false;
18
19 // … do something useful with all the allocated resources …
20
21 // No clean-up necessary. Also, no try/catch is necessary here, at least not
22 // for resource management reasons.
23 return true;
24}
This is a nice solution to the problem. However, it comes with some caveats which I’ll discuss in the next sections.
If defined as above, the ScopeGuard
can be copied and moved.1 This requires some attention since:
ScopeGuard
, it will be destroyed twice and the contained (copied)
callable will be called twice andScopeGuard
, we must make sure the moved-from instance does not
try to call the moved-from callable.To make copying possible one would probably need to implement some kind of reference counting (as
std::shared_ptr
does). I’m not going to go into that. Moving would be a lot easier, one would just
need to prevent the callable in the moved-from scope guard to be called. However, I find that I
actually never need it to be movable, so I just forbid everything:
1template<class Func>
2class ScopeGuard {
3public:
4 ScopeGuard(const ScopeGuard<Func> &) = delete;
5 ScopeGuard<Func> &operator=(const ScopeGuard<Func> &) = delete;
6 ScopeGuard(ScopeGuard<Func> &&) = delete;
7 ScopeGuard<Func> &operator=(ScopeGuard<Func> &&) = delete;
8 …
9};
One reason for using scope guards is cleaning up when exception handling unwinds the stack. That
means that any exception thrown from the stored callable can become problematic. The question “what
happens if an exception is thrown during exception handling” is tricky and the behavior is often
misunderstood. The short version is: Everything is fine if the second exception is thrown while the
first exception has already been caught and is being handled, i.e., while the catch
block is
running. If on the other hand the second exception is thrown before the first exception has been
caught, the program terminates.
What happens before the first exception is caught, i.e., before the catch
block runs? Stack
unwinding. At which point is our storedFunc
executed? During stack unwinding! Basically, if
anything in storedFunc
throws an exception, that’s the end of our program.
So it makes sense to make sure that storedFunc
does not throw (or at least claims not to throw).
In C++20, it’s good practice to constrain template parameters via concepts, so we can do something
like this:
1template<typename T>
2concept CallableInScopeGuard = requires(const T& callable) {
3 // The callable needs to be callable with no arguments, be noexcept, and
4 // should return void since we're discarding the return value.
5 { callable() } noexcept -> std::same_as<void>;
6};
7
8template<CallableInScopeGuard Func>
9class ScopeGuard {
10 // …
11private:
12 Func storedFunc;
13};
(You can try it here at Godbolt.)
This not only makes sure that only noexcept
qualified callables are being used, but also nicely
communicates that whatever is used as callable must be invocable with no arguments and should not
return anything. Note that with this, you need to pass a lambda to the scope guard explicitly marked
as noexcept
, like this:
1char * foo = new char[100];
2auto guard = ScopeGuard([&foo]() noexcept { delete[] foo; });
A final consideration may be performance. How much slower is
1auto someGuard = ScopeGuard([&something]() {
2 delete something; });
3…
4// Implicit on scope end:
5~someGuard();
compared to just:
1…
2// at the end of the scope
3delete something;
The answer is: With a modern compiler and even at low optimization levels, there should be no
difference since this is compiled to the same machine code (at least with GCC on -O1
), as you can
see here on Godbolt.
Personally, I basically use the ScopeGuard
class as introduced above, with the two tweaks as
explained, in practice. You can find the full copy-and-paste-ready source below:
ScopeGuard
I use 1#include <memory>
2
3template<typename T>
4concept CallableInScopeGuard = requires(const T& callable) {
5 { callable() } noexcept -> std::same_as<void>;
6};
7
8template<CallableInScopeGuard Func>
9class ScopeGuard {
10public:
11 ScopeGuard(Func f) : storedFunc(f) {}
12 ~ScopeGuard() { storedFunc(); }
13
14 ScopeGuard(const ScopeGuard<Func>&) = delete;
15 ScopeGuard<Func>& operator=(const ScopeGuard<Func>&) = delete;
16 ScopeGuard(ScopeGuard<Func>&&) = delete;
17 ScopeGuard<Func>& operator=(ScopeGuard<Func>&&) = delete;
18private:
19 Func storedFunc;
20};
(You can find it to play around with at Godbolt.)
However, I’m certainly not the first or only one to provide a possible implementation.
One of the first implementations I could find is by Andrei Alexandrescu and Petru Marginean in their article Generic: Change the Way You Write Exception-Safe Code - Forever. The article is from the year 2000 and consequently, their technique could not use any of the nice additions that C++{11,14,17,20} brought us. Thus, while it’s an interesting read, I would not use it verbatim in a modern project today.
A more recent off-the-shelf variant (that has a real chance of making it into the standard, as far
as I can tell) is currently contained in the Extensions for Library Fundamentals v3 and thus
“standardized” in the std::experimental
namespace. It seems to be available in GCC 13’s STL
implementation, but not in Clang’s libc++
or in GCC 12, as seen here on Godbolt.2
This approach’s equivalent of our ScopeGuard
is called std::experimental::scope_exit
(see its
documentation). It has one major additional feature compared to ‘our’ ScopeGuard
: It can be
active
or inactive
. A scope_exit
created with a callable is active by default - if it
becomes inactive (e.g. by calling release()
on it), the stored callable will not be called once
the scope_exit
goes out of scope. This can be handy if one decides at some point that cleanup
is not necessary anymore.
It also comes with two “variants”: std::experimental::scope_fail
and
std::experimental::scope_success
. While std::experimental::scope_fail
only calls its stored
callable only when the scope is exited via an exception (during stack unwinding),
std::experimental::scope_success
does the opposite and only calls its stored callable if the scope
is exited “normally”.
An interesting thing to note is that the suggested implementation in P0052R2, which is the proposal
by Peter Sommerlad and Andrew L. Sandoval that std::experimental::scope_exit
and its siblings is
based on, would add more interesting functionality: The proposal contains a variant called
unique_resource
, which bundles the scope guard (i.e., the function to free the resource) with the
resource itself. It also comes with a factory method make_unique_resource_checked
, which allows to
compare the created resource (e.g., a file descriptor) to a “failure value” (e.g. -1
for file
descriptors). If the creation of the resource fails, the passed-in cleanup function will never be
called. Quite handy, but I have not seen an actual implementation of that yet.
A solution I have seen used a couple of times, and which has the charm of needing nothing but C++11, is
(ab)using an empty std::unique_ptr
with a custom deleter like this:
1auto myDeleter = [](void*) { /* do whatever you need to do */ };
2std::unique_ptr<void, decltype(myDeleter)> guard(1, myDeleter);
This feels a bit clumsy: you cannot specify the lambda directly in the argument, you need to use
decltype
, and what’s that weird 1
doing there? I’ve seen some solutions use class template
argument deduction (CTAD) to improve it like this:
1/* WARNING! THIS IS NOT PORTABLE! */
2template<class T>
3using ScopeGuard = std::unique_ptr<void, T>;
4
5void someFunction()
6{
7 ScopeGuard guard((void*)1, [](void*) { /* … whatever … */ });
8}
This looks like a semi-decent way of using the scope guard - there’s still that pesky 1
which has
no function, but at least we don’t need to specify the type manually anymore. However, it is not
portable! This only works if std::unique_ptr
is defined as a class
(and not a struct
), because
CTAD is only defined for classes, not structs. And oh look what Clang’s libc++ does…
One could of course get around this by defining a class template which wraps std::unique_ptr
or
something similar, but I think at this point one can also just use one’s own ScopeGuard
class
altogether.
Whatever method one chooses, I think adding scope guards to one’s regular C++ vocabulary is an important step towards having less resource leaks. They are certainly not a silver bullet which solves every resource management problem, but where they fit, they provide a nice “fire and forget” approach to cleaning up your resources.
Assuming that you did not use a
type as Func
which forbids copying and/or moving. This happens e.g. if your Func
is a lambda
type and your lambda captured a type which cannot be copied. ↩︎
Though as of 2024-01-12, the libstdc++ implementation status page does not list this feature as implemented, so YMMV. ↩︎
You can use your Mastodon account to reply to this post.