The Simplest and Neatest C++11 Scopeguard

The simplest and neatest c++11 ScopeGuard

Boost.ScopeExit is a macro that needs to work with non-C++11 code, i.e. code that has no access to lambdas in the language. It uses some clever template hacks (like abusing the ambiguity that arises from using < for both templates and comparison operators!) and the preprocessor to emulate lambda features. That's why the code is longer.

The code shown is also buggy (which is probably the strongest reason to use an existing solution): it invokes undefined behaviour due to returning references to temporaries.

Since you're trying to use C++11 features, the code could be improved a lot by using move semantics, rvalue references and perfect-forwarding:

template< typename Lambda >
class ScopeGuard
{
bool committed; // not mutable
Lambda rollbackLambda;
public:


// make sure this is not a copy ctor
template <typename L,
DisableIf<std::is_same<RemoveReference<RemoveCv<L>>, ScopeGuard<Lambda>>> =_
>
/* see http://loungecpp.net/w/EnableIf_in_C%2B%2B11
* and http://stackoverflow.com/q/10180552/46642 for info on DisableIf
*/
explicit ScopeGuard(L&& _l)
// explicit, unless you want implicit conversions from *everything*
: committed(false)
, rollbackLambda(std::forward<L>(_l)) // avoid copying unless necessary
{}

template< typename AdquireLambda, typename L >
ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l))
{
std::forward<AdquireLambda>(_al)(); // just in case the functor has &&-qualified operator()
}

// move constructor
ScopeGuard(ScopeGuard&& that)
: committed(that.committed)
, rollbackLambda(std::move(that.rollbackLambda)) {
that.committed = true;
}

~ScopeGuard()
{
if (!committed)
rollbackLambda(); // what if this throws?
}
void commit() { committed = true; } // no need for const
};

template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuard( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}

template<typename rLambda>
ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
{
return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
}

C++11 scope exit guard, a good idea?


But is it a good idea?

Sure. A related topic is the RAII paradigm.

Or are there
potential problems I have missed?

You don't handle exceptions.

Is
there already a similar solution (with
C++0x features) in boost or similar?

Alexandrescu came up with ScopeGuard a long time back. Both Boost and std::tr1 has a thing called scoped_ptr and shared_ptr (with a custom deleter) that allows you to accomplish just this.

How to implement Scope Guard that restores value upon scope exit?

Your move constructor leaves the pointer member uninitialized, so the rvalue object ends up holding a junk pointer, which it dereferences in its destructor. That's a bug. You should initialize it to nullptr and check for nullptr in the destructor.

For a type like this I would not expect move assignment to be a simple swap, I would expect the rvalue to end up not owning anything. So I would implement the move like this instead, so the rvalue ends up empty:

ValueScopeGuard& operator=(ValueScopeGuard&& other)
{
ValueScopeGuard(std::move(other)).swap(*this);
return *this;
}

The name makeValueScopeGuard isn't clear to me that it changes the value itself, I'd expect it to just copy the current value and restore it in the destructor.

As far as existing types go, the closest I can think of is the Boost I/O state savers, which do not alter the current state they just copy it and restore it.

Does ScopeGuard use really lead to better code?

It definitely improves your code. Your tentatively formulated claim, that it's obscure and that code would merit from a catch block is simply not true in C++ because RAII is an established idiom. Resource handling in C++ is done by resource acquisition and garbage collection is done by implicit destructor calls.

On the other hand, explicit catch blocks would bloat the code and introduce subtle errors because the code flow gets much more complex and resource handling has to be done explicitly.

RAII (including ScopeGuards) isn't an obscure technique in C++ but firmly established best-practice.

C++ scope guard with zero overhead

If by overhead you mean how much space is occupied by scope-guard variable then zero overhead is possible if functional object is compile-time value. I've coded small snippet to illustrate this:

Try it online!

#include <iostream>

template <auto F>
class ScopeGuard {
public:
~ScopeGuard() { F(); }
};

void Cleanup() {
std::cout << "Cleanup func..." << std::endl;
}

int main() {
{
char a = 0;
ScopeGuard<&Cleanup> sg;
char b = 0;
std::cout << "Stack difference "
<< int(&a - &b - sizeof(char)) << std::endl;
}
{
auto constexpr f = []{
std::cout << "Cleanup lambda..." << std::endl; };

char a = 0;
ScopeGuard<f> sg;
char b = 0;
std::cout << "Stack difference "
<< int(&a - &b - sizeof(char)) << std::endl;
}
}

Output:


Stack difference 0
Cleanup func...
Stack difference 0
Cleanup lambda...

Code above doesn't create even a single byte on a stack, because any class variable that has no fields occupies on stack 0 bytes, this is one of obvious optimizations that is done by any compiler. Of course unless you take a pointer to such object then compiler is obliged to create 1-byte memory object. But in your case you don't take address to scoped guard.

You can see that there is not a single byte occupied by looking at Try it online! link above the code, it shows assembler output of CLang.

To have no fields at all scoped guard class should only use compile-time function object, like global function pointer of lambda without capture. This two kinds of objects are used in my code above.

In code above you can even see that I outputted stack difference of char variable before and after scoped guard variable to show that scoped guard actually occupies 0 bytes.


Lets go a bit further and make possibility to have non-compile-time values of functional objects.

For this again we create class with no fields, but now store all functional objects inside one shared vector with thread local storage.

Again as we have no fields in class and don't take any pointer to scoped guard object then compiler doesn't create not a single byte for scoped guard object on stack.

But instead single shared vector is allocated in heap. This way you can trade stack storage for heap storage if you're out of stack memory.

Also having shared vector will allow us to use as few memory as possible, because vector uses only as much memory as many there are nested blocks that use scoped guard. If all scoped guards are located sequentially in different blocks then vector will have just 1 element inside so using just few bytes of memory for all scoped guards that were used.

Why heap memory of shared vector is more economical memory-wise than stack-stored memory of scoped guard. Because in case of stack memory if you have several sequential blocks of guards:

void test() {
{
ScopeGuard sg(f0);
}
{
ScopeGuard sg(f1);
}
{
ScopeGuard sg(f2);
}
}

then all 3 guards occupy tripple amount of memory on stack, because for each function like test() above compiler allocates stack memory for all used in function's variables, so for 3 guards it allocates tripple amount.

In case of shared vector test() function above will use just 1 vector's element, so vector will have size of 1 at most hence will use just single amount of memory to store functional object.

Hence if you have many non-nested scoped guards inside one function then shared vector will be much more economical.

Now below I present code snippet for shared-vector approach with zero fields and zero stack memory overhead. To remind, this approach allows to use non-compile-time functional objects unlike solution in part one of my answer.

Try it online!

#include <iostream>
#include <vector>
#include <functional>

class ScopeGuard2 {
public:
static auto & Funcs() {
thread_local std::vector<std::function<void()>> funcs_;
return funcs_;
}
ScopeGuard2(std::function<void()> f) {
Funcs().emplace_back(std::move(f));
}
~ScopeGuard2() {
Funcs().at(Funcs().size() - 1)();
Funcs().pop_back();
}
};

void Cleanup() {
std::cout << "Cleanup func..." << std::endl;
}

int main() {
{
ScopeGuard2 sg(&Cleanup);
}
{
auto volatile x = 123;
auto const f = [&]{
std::cout << "Cleanup lambda... x = "
<< x << std::endl;
};

ScopeGuard2 sg(f);
}
}

Output:

Cleanup func...
Cleanup lambda... x = 123

How to avoid warning when using scope guard?

You can disable this warnings by -Wno-unused-variable, though this is a bit dangerous (you loose all realy unused variables).

One possible solution is to actually use the variable, but do nothing with it. For example, case it to void:

(void) g;

which can be made into a macro:

#define IGNORE_UNUSED(x) (void) x;

Alternatively, you can use the boost aproach: declare a templated function that does nothing and use it

template <typename T>
void ignore_unused (T const &) { }

...

folly::ScopeGuard g = folly::makeGuard([&] {close(sock);});
ignore_unused(g);

Is there a C++ standard class to set a variable to a value at scope exit

Not yet (there have been proposals for this). But implementing a generic one is simple enough;

struct scope_exit {
std::function<void()> f_;
explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
~scope_exit() { if (f_) f_(); }
};
// ...
m_theVariableToChange = true;
scope_exit resetFalse([&m_theVariableToChange]() { m_theVariableToChange = false; });

For simplicity above, I've redacted the copy and move constructors etc...

Marking them as = delete will make the above a minimal solution. Further; moving could be allowed if desired, but copying should be prohibited.


A more complete scope_exit would look like (online demo here);

template <typename F>
struct scope_exit {
F f_;
bool run_;
explicit scope_exit(F f) noexcept : f_(std::move(f)), run_(true) {}
scope_exit(scope_exit&& rhs) noexcept : f_((rhs.run_ = false, std::move(rhs.f_))), run_(true) {}
~scope_exit()
{
if (run_)
f_(); // RAII semantics apply, expected not to throw
}

// "in place" construction expected, no default ctor provided either
// also unclear what should be done with the old functor, should it
// be called since it is no longer needed, or not since *this is not
// going out of scope just yet...
scope_exit& operator=(scope_exit&& rhs) = delete;
// to be explicit...
scope_exit(scope_exit const&) = delete;
scope_exit& operator=(scope_exit const&) = delete;
};

template <typename F>
scope_exit<F> make_scope_exit(F&& f) noexcept
{
return scope_exit<F>{ std::forward<F>(f) };
}

Notes on the implementation;

  • std::function<void()> can be used to erase the type of the functor. std::function<void()> offers exception guarantees on the move constructors based on the exception specific of the held function. A sample of this implementation is found here
  • These exception specifications are consistent the C++ proposal and GSL implementations
  • I've redacted most of the motivation for the noexcept, more substantial detail is found in the C++ proposal
  • The "usual" RAII semantics of the destructor, hence the "scope exit" function is applicable; it will not throw, this is also consistent with the C++11 specification on the default exception specification for a destructor. See cppreference, SO Q&A, GotW#47 and HIC++

Other implementations can be found;

  • The C++ proposal mentioned above and its revision (as of this writing)
  • Boost.ScopeExit
  • The Microsoft GSL implementation


Related Topics



Leave a reply



Submit