scoped_guard: a practical use of RAII
While exploring RAII, I found this post scope(exit) in C++ 11 that talks about a technique also called scope guard. The concept of “scope guard” was proposed by Andrei Alexandrescu and Petru Marginean in an article on the C/C++ Users Journal (December 2000). The same technique has been proposed for standardization (see N4189) but is still not part of the standard library. This technique employes RAII to execute a cleanup routine that is bound by scope ensuring the execution at scope exit unless released early. Here a simple C++ 14 implementation:
#include <cstdio>
#include <cstdlib>
#include <memory>
template<typename F>
class scope_guard {
public:
// Prevent copying to avoid call destructor on each copy
scope_guard(const scope_guard&) = delete;
scope_guard& operator=(const scope_guard&) = delete;
// Accept lvalue function objects
scope_guard(const F& func)
: rollback_func(func) {
}
// Accept rvalue function objects
scope_guard(F&& func)
: rollback_func(std::move(func)) {
}
// Allow moving
scope_guard(scope_guard&& other)
: rollback_func(std::move(func)), dismissed(other.dismissed) {
other.dismissed = true;
}
// Invoke function object only if it hasn't been moved or manually dismissed
~scope_guard() {
if (!dismissed) {
rollback_func();
}
}
void dismiss() {
dismissed = true;
}
private:
F rollback_func;
bool dismissed = false;
};
template<typename F>
scope_guard<typename std::decay<F>::type> make_scope_guard(F&& f) {
return scope_guard<typename std::decay<F>::type>(std::forward<F>(f));
}
bool foo() noexcept {
std::cout << "foo!" << std::endl;
return true;
}
void bar() noexcept {
std::cout << "bar!" << std::endl;
}
class Func {
public:
void operator()() const {
std::cout << "Func object called!" << std::endl;
}
};
void main() {
auto scope_guard1 = make_scope_guard(foo);
auto scope_guard2 = make_scope_guard(&bar);
auto scope_guard3 = make_scope_guard(Func());
Func f1;
auto scope_guard4 = make_scope_guard(f1);
auto scope_guard5 = make_scope_guard([]() {
std::cout << "lambda called!" << std::endl;
});
}
There is an interesting and alternative approach to achieve the same result without the need of creating an ad-hoc solution: use an std::unique_ptr.
std::unique_ptr is a template class with the following signatures:
template<class T, class Deleter = std::default_delete<T>>
//class unique_ptr;
template <class T, class Deleter>
//class unique_ptr<T[], Deleter>;
Focusing on the non-array version of std::default_delete we can observe that it is any functor/function that has the signature void operator()(T* ptr) const
. A simple example of this, it can be the following:
using file_ptr = std::unique_ptr<FILE, decltype(&std::fclose)>;
{
auto file = file_ptr(std::fopen(<somefile>, “r”), &std::fclose);
//do something with file
}
Finally there is a handy way to release early the guard context: use the std::unique_ptr::release()
function.