Skip to content

← RAII: Objects With Lifetimes step 2 of 4

Medium Primitives

Write the guard yourself

A scope guard pairs an action at construction with cleanup at destruction. Complete a small guard that increments a counter when it is created and decrements the same counter when it goes out of scope.

This is an example of RAII: tying resource management to object lifetime. Standard types such as std::lock_guard and std::unique_ptr use this idea, though their interfaces and ownership rules differ.

One acquisition, one release

CounterGuard stores a reference to a Counter. Its constructor calls acquire() once; its destructor calls release() once. The counter must outlive every guard referring to it. The supplied calls keep that lifetime relationship intact.

Each recursive call in nest creates one guard before calling itself again. The earlier guards stay alive during that call. As the calls return, the guards are destroyed in reverse order and the live count falls back to zero.

Destructors also clean up fully constructed local guards during ordinary exception unwinding. This does not promise cleanup after every kind of process termination, or destruction of an object whose constructor failed.

Prevent accidental copies

A compiler-generated copy constructor would copy the reference without calling acquire() again. Two guards would then release a counter that was acquired only once. For this guard, prevent copying explicitly:

CounterGuard(const CounterGuard&) = delete;
CounterGuard& operator=(const CounterGuard&) = delete;

The reference member already prevents generated copy assignment, but the explicit deletion documents the class's intended interface. No move operations are needed for this exercise. With these declarations and a destructor, none are generated automatically.

This is a choice for this scope guard, not a rule that every resource-managing class must forbid copying. Other classes can define safe copying or shared ownership with different semantics.

Keep the constructor explicit. It permits direct construction such as CounterGuard guard{counter} while preventing an implicit conversion from Counter& to a guard, which would change the live count as a side effect.

Your task

Complete only CounterGuard. Keep the supplied Counter, nest and max_live implementations.

int max_live(Counter& counter, const std::vector<int>& depths);

For each positive depth d, the supplied recursion creates d simultaneously live guards, then destroys them all before processing the next depth. Zero or negative depths create none. Depths are small enough for this recursive exercise; it is not a technique for arbitrarily deep input.

Counter::acquire() updates the current live count and its historical peak. The harness starts with a fresh counter, calls max_live, and reports [peak, live_afterwards]. For {1, 3, 2}, that is [3, 0], not [6, 0]: the depths are processed separately. Empty input and all nonpositive depths produce [0, 0].

The final zero checks for unreleased guards. These output checks do not copy a guard, so passing them alone does not establish the required noncopyable interface. Complete the deleted copy declarations as well as the constructor and destructor.