We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← RAII: Objects With Lifetimes step 1 of 4
Observe cleanup at the end of a scope
A local object's destructor runs when execution leaves its scope normally, including through an early return. Destructors also run for completed local objects when an exception unwinds their scopes on its way to a handler.
Resource-owning types use this behavior to release files, memory or locks. This pattern is called RAII, short for Resource Acquisition Is Initialization: an object takes responsibility for a resource and releases it when its lifetime ends. Callers use the owning object instead of repeating cleanup on every path.
Destruction order
Completed local objects in the same scope are destroyed in reverse construction order. Using the Tracer supplied below:
std::vector<std::string> log;
{
Tracer a{"a", log};
Tracer b{"b", log};
}
// log: ["+a", "+b", "-b", "-a"]
The log outlives both tracers, so their destructors can still append to it. This order helps when one local object depends on another, but you must still arrange the lifetimes correctly. A pointer to an already-destroyed object does not become safe just because it is stored in an RAII type.
Cleanup is not guaranteed after every form of termination. For example, std::exit does not destroy local automatic objects, and std::abort skips destructors. If a constructor throws before an object finishes construction, that object's destructor does not run; its completed base and member objects are cleaned up instead.
Your task
Implement:
std::vector<std::string> run(const std::vector<std::string>& names, int stop_after);
The provided Tracer appends "+" + name when constructed and "-" + name
when destroyed. Keep this type unchanged. Create one local Tracer at a time,
using names in input order, and let its scope end before creating the next.
A loop body is already a scope; an extra nested block is optional.
Create at most stop_after tracers. Zero or negative stop_after means none; a value larger than the number of names means process every name. Empty input returns an empty log. Input lengths and stop_after fit int. Preserve each name exactly, including empty strings or repeated names.
For names ["a", "b", "c"] and stop_after = 2, return:
["+a", "-a", "+b", "-b"]
Do not call destructors manually or append the expected strings yourself. Use the supplied Tracer and its lifetime to produce the entries. The starter creates no tracers, so it always returns an empty log.
Finish the tracer's scope before returning the log
Stop by limiting the loop, breaking after a completed iteration, or checking the limit before constructing another tracer. Return log after all tracers have been destroyed.
A return statement initializes its result before destroying local objects. If you return log while a tracer is still alive, log may be moved into the return value before the destructor appends its final entry. Copy elision can hide this mistake, so do not rely on it. Ending the tracer's scope first makes the returned contents independent of that optimization.
Tracer catches failures while writing its destructor entry because that write can allocate. This teaching helper may lose the entry if allocation fails; the exercise assumes these small log writes succeed. Real resource wrappers need a cleanup and error-reporting strategy appropriate to their resource, rather than treating a catch-all as a universal solution.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
C++ reference solution
Sign in to track your attempts toward unlocking the reference solution.