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 task
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)
Challenge 2
Write ValueRestorer, a guard for "try a change, undo it if we don't finish": ValueRestorer(int& target) remembers the target's current value, the destructor puts that value back unless commit() was called. Make it non-copyable.
Challenge 3
Write Indent, an RAII helper for nested output: a static int level shared by all objects goes up by one in the constructor and down by one in the destructor, and static std::string pad() returns two spaces per level. Make it non-copyable, because copying would unbalance the level.