We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← RAII: Objects With Lifetimes step 4 of 4
Close a transaction when an operation throws
A cleanup call after an operation is skipped if that operation throws:
ledger.open();
ledger.add(delta); // may throw
ledger.close();
A local guard can pair these operations. Its constructor opens the transaction; its destructor closes it. Once construction succeeds, the guard's destructor runs at normal scope exit and when that scope is unwound toward an exception handler. Cleanup belongs to the guard instead of being repeated at each exit.
Cleanup and state are separate responsibilities
Exception guarantees describe what callers can rely on if an operation fails:
| Guarantee | Meaning |
|---|---|
| No-throw | The operation does not let an exception escape. |
| Strong | Failure leaves the state covered by the operation's contract unchanged. |
| Basic | Failure preserves valid state and avoids resource leaks, but values may change. |
RAII helps with resource cleanup. It does not automatically establish the basic guarantee: an operation could release every resource yet leave two related fields inconsistent. It also does not undo changes made before a failure. A strong guarantee often requires preparing changes separately and committing them with an operation that cannot fail.
Cleanup destructors should not throw. A non-throwing swap can also be useful for committing a prepared result, but not every swap is inherently non-throwing. Declaring a function noexcept is a promise about escaping exceptions; it does not make operations inside the function incapable of failing.
Your task
Implement:
int apply_all(Ledger& ledger, const std::vector<int>& deltas);
Keep the supplied Ledger unchanged. For each delta, in input order:
- Construct a local transaction guard that calls ledger.open().
- Call ledger.add(delta).
- Let the guard's destructor call ledger.close() exactly once.
Use one guard per iteration. Make the guard noncopyable so copying it cannot create two objects that both close the same transaction. The referenced Ledger must outlive the guard.
Ledger::add throws std::runtime_error for exactly 13, before changing the balance. Catch that exception around the loop, stop processing, and return ledger.balance(). Successful earlier updates remain applied; later deltas are skipped. Negative deltas and zero are ordinary updates.
For [10, 20, 13, 30], the first two updates produce 30. The third opens a
transaction and throws; unwinding closes it before the catch runs. The final
delta is skipped, and the result is 30. This is partial progress, not rollback
of the whole input. Here a transaction only records a matched open and close;
it is not a database transaction with automatic rollback.
Empty input leaves the ledger unchanged. If the first delta is 13, no delta is applied. The harness starts with a zero balance, but apply_all should use the ledger it receives without resetting it. Test balances, intermediate sums and transaction counts stay within int's range.
What the harness checks
It reports [returned_balance, opened_minus_closed]. For the example above,
expect [30, 0]. A positive difference means unmatched opens; a negative one
means too many closes. Zero is necessary, but the counter alone cannot prove
that every open was paired correctly at the right time.
The starter returns the expected balance but leaves one transaction open on the throwing path. Remove its manual open/close pair from the loop and use the guard instead. Do not close again in the catch block: unwinding has already destroyed the guard before control reaches that handler.
Ledger::open and close only increment counters and do not throw for the supported inputs. A real guard must also account for failures during acquisition and ensure its cleanup can safely run during unwinding.
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.