Step 8 of 8
Challenge: a scope guard
RAII isn't only about memory. Any action that must happen when you leave a scope can be tied to a destructor: closing a file, unlocking a mutex, rolling back a half-finished transaction, restoring a setting you changed temporarily, printing "done" in a log.
A scope guard is a small, general-purpose RAII class: it holds a function and calls it from its destructor. Many large codebases have one (C++ proposals call it scope_exit).
std::function and lambdas
std::function<void()> (from <functional>) can store any callable that takes no arguments and returns nothing: a plain function, or a lambda. A lambda is an unnamed function written inline, with a capture list saying which outside variables it may use: [&count]() { count++; } captures count by reference. Lambdas get a full module later; this is enough to use them here.
#include <functional>
#include <iostream>
class OnExit {
public:
explicit OnExit(std::function<void()> f) : f_(f) {}
~OnExit() { f_(); }
OnExit(const OnExit&) = delete;
OnExit& operator=(const OnExit&) = delete;
private:
std::function<void()> f_;
};
int main() {
int depth = 0;
{
depth++;
OnExit undo([&depth]() { depth--; });
std::cout << "inside, depth " << depth << "\n";
}
std::cout << "after, depth " << depth << "\n";
}
inside, depth 1
after, depth 0
Dismissing
A scope guard is often used for rollback: set it up at the start of an operation, and if the operation completes successfully, call dismiss() so the undo doesn't run. On any early exit (an error return), the destructor still rolls things back. A bool member that dismiss() sets is enough; the destructor checks it before calling the function.
A guard must not be copyable, or two copies would run the action twice.
Your turn: write class ScopeGuard:
explicit ScopeGuard(std::function<void()> f)- the destructor calls
funless dismissed void dismiss()- it must not be copyable (two copies would run the action twice)