We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← RAII: Objects With Lifetimes step 2 of 4
Write the guard yourself
std::lock_guard, std::unique_ptr, std::fstream, std::scoped_lock —
every one of these is the same twenty lines. Writing those twenty lines once
is the difference between using them and understanding them.
The shape
class Guard {
public:
explicit Guard(Resource& r) : r_(r) { r_.acquire(); } // acquire
~Guard() { r_.release(); } // release
Guard(const Guard&) = delete; // and this
Guard& operator=(const Guard&) = delete;
private:
Resource& r_;
};
Four members, and the last two are the ones people leave out.
Why copying must be deleted
Suppose you allow it. Now:
Guard a{resource};
Guard b = a; // copy — acquired once, about to be released twice
Both destructors run. The resource is released twice: a lock unlocked twice, a pointer freed twice, a refcount driven below zero. Double-free is one of the classic memory-safety bugs, and this is the shape it usually arrives in.
So a guard deletes its copy operations, and the compiler turns a whole
category of bug into a compile error. = delete is not a hint; the function
exists, overload resolution picks it, and using it fails to compile with a
message naming the deleted function.
You will meet the fuller version of this rule in Track 3 as the rule of five. Today the short version is enough: if the destructor does something, copying is probably wrong.
explicit, and why it is there
explicit Guard(Resource& r)
Without explicit, a single-argument constructor is an implicit conversion
— the compiler is allowed to build a Guard out of a Resource silently, in
any context that wants one. For a type whose construction acquires a lock,
being constructed by accident is exactly what you do not want. Track 6 gives
this its own problem; for now, one-argument constructors get explicit unless
you have a reason.
Your task
A Counter is given: acquire() increments a live count and a total,
release() decrements the live count. Write the guard.
class CounterGuard {
// acquire in the constructor, release in the destructor,
// no copying
};
int max_live(Counter& counter, const std::vector<int>& depths);
depths describes nesting: for each entry d, create d nested guards, then
let them all go out of scope. Return the highest live count ever observed.
For depths = {1, 3, 2} the answer is 3 — three guards alive at once during
the middle entry, and none left alive between entries.
The starter’s guard compiles and does nothing — it stores the counter and
never touches it — so every test comes back [0, 0]. The recursion is
already written.
Note the second number the tests check: the live count after everything has gone out of scope. It is there to catch a guard that acquires and forgets to release, which would otherwise pass on the peak alone.
Stuck?
C++ reference solution
Sign in to attempt this problem and reveal the reference solution.