C++ Exceptions: try, catch and Safe Cleanup
Learn C++ try, throw and catch in simple steps, trace safe cleanup, and protect existing data by checking a replacement before committing it.
What you will learn
- Read try, throw and catch as a clear failure path.
- Catch a standard exception by const reference.
- Trace cleanup before the matching handler runs.
- Explain why catch does not undo earlier changes.
- Validate replacement data before changing current state.
An exception reports that an operation could not finish
Imagine a function that must load a valid group of readings. It cannot do its job if one reading breaks the required rule. An exception is one way to report that failure to a caller. It interrupts the normal path and looks for a matching handler. It is not a second return value that the caller can ignore while continuing with a half-created answer.
The three main words are try, throw and catch. Put an operation inside a try block when that part of the program is ready to handle a failure. throw reports a failure by creating an exception object. catch describes a handler for a suitable type. When the handler finishes normally, execution continues after the whole try-and-catch statement, not at the line that threw.
Do not use exceptions for every ordinary choice. A search with no match can return optional, as in the previous lesson. An expected menu choice can use a normal condition. An exception is useful when a function cannot complete its promised operation and a caller farther out should decide how to report or recover. The choice depends on the interface, not on a rule that all errors must look identical.

Follow a small failure from function to caller
The function cannot accept a negative count. The custom message is stable; unrelated system exception messages can vary.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Rejected: Count must not be negative
Finished
After editing inputs, calculate a new prediction; this reference describes only the starter.
The success line is skipped when the call throws
Read the example from main. The try block calls requireCount with minus one. The function checks the number and throws invalid_argument. It does not return normally to the next output statement, so Accepted is not printed. The matching catch receives the exception, prints its message, and finishes. The final Finished line is outside the handler and runs afterward. Write these steps beside the source before trying another input.
Catch the exception as const std::invalid_argument&. The reference avoids making an unnecessary value copy, and const says that this handler only reads the exception. what returns a description that the handler can display while the exception remains available. We use a message written in our own source so the expected transcript is predictable. Do not build tests around the exact wording of an operating system or library diagnostic that can differ across installations.
Handlers are tried in their written order. Put a specific derived exception handler before a more general base handler when both are needed. catch(const std::exception&) handles many standard exception types, but not every possible thrown type. A catch-all handler uses an ellipsis and belongs last. Avoid an empty handler that silently turns failure into apparent success; the caller still needs an honest result or a deliberate recovery decision.
- The caller enters try and calls requireCount(-1).
- throw stops that normal path; Accepted is skipped.
- The matching catch prints the rejection message.
- Execution continues after the handlers and prints Finished.
Watch cleanup happen before catch
The local guard is fully constructed before the throw. Its destructor runs as that scope is left.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Cleaned up
Handled failure
After editing inputs, calculate a new prediction; this reference describes only the starter.
Cleanup is not the same as undo
Leaving scopes while an exception travels toward a handler is called stack unwinding. In this example, the completed guard object is destroyed before the matching handler begins. Standard owning objects, such as vectors and smart pointers, also release their resources when their lifetimes end. This is why RAII is useful on failure paths as well as success paths. You do not need a separate delete statement in every catch block.
This cleanup rule does not mean all program changes are reversed. If a function changes a shared number and then throws, that number does not automatically return to its old value. If it writes a file, catching the exception does not erase that write. Keep resource cleanup and application state as separate questions. Ask what was acquired, what was changed, and which guarantees the operation actually offers to its caller.
Do not throw from a destructor during unwinding. A second exception escaping that cleanup can terminate the program. Destructors should release resources safely, with a separate explicit operation for reporting failures when needed. Also do not add noexcept merely because it sounds safe: if an exception escapes a noexcept function, the program terminates. Our small handlers catch the failures they understand; they do not claim to recover from every possible system failure.
Reject a replacement without changing the old data
The replacement is checked first. swap is reached only for an accepted candidate.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Rejected
Current: 10
After editing inputs, calculate a new prediction; this reference describes only the starter.
Build and check the new state before replacing the old state
The third example keeps current unchanged while checking candidate. Candidate is a separate vector value. If any reading is below zero or above twenty, the function throws before swap. The caller can still use its original vector containing ten. If every reading passes, swap exchanges the complete vectors. The old elements then belong to candidate and are cleaned up when that local object leaves scope.
The strong exception guarantee means an operation either succeeds or leaves the relevant observable state unchanged on failure. This example illustrates that plan for current with its ordinary vector allocator and a non-throwing swap. It does not promise to reverse unrelated logging or external writes. The basic guarantee is weaker: the object remains valid without leaked resources, but its value may have changed. Always name which state a guarantee covers.
A common broken version clears current before validating each incoming reading. If a later check fails, the caller loses its old data. Moving the check into catch does not repair that design. Repair the order instead: build a candidate, validate it, then commit. Test a failure at the first and at the last candidate element. A last-element failure is especially useful because it catches code that updates current gradually before discovering a problem.
Keep one trusted state while testing a replacement
Start current with the reading ten. Read a count from zero through five and that many whole integer lines. Malformed or missing input prints Invalid input and returns status one before replacement is attempted. Any well-formed integer may be collected as a candidate reading; the replacement operation itself enforces zero through twenty. This separates understanding the text from accepting its meaning for the task.
On an accepted candidate, print Accepted and the new State row with status zero. On an out-of-range reading, catch the typed exception, print Rejected: Reading out of range and the unchanged State row, then return status two. A zero-count candidate is a valid empty replacement. Test both endpoints, a negative number, twenty-one, failure after valid readings, and numeric overflow in input. Explain which branch reports parsing failure and which branch reports a failed domain operation.
Compile and check one complete program
Save one example as main.cpp. Choose your installed compiler. Run only after a successful build. The in-page editor needs the configured compiler sandbox; these terminal commands are an alternative, not proof that the sandbox is running.
macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson
Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Replace a reading group only after every candidate passes validation.
Acceptance checklist
- Start current as {10}; read count 0..5 and that many whole integer lines.
- For malformed, missing or bad count input print Invalid input and return 1 before replacement.
- Validate candidate readings in 0..20 inside replaceReadings and throw out_of_range on rejection.
- Commit with vector swap only after all readings pass; never clear current first.
- Print Accepted and State with status 0, or the rejection and unchanged State with status 2; allow an empty replacement.
Need a hint?
- Collect input in a separate candidate vector.
- Take candidate by value so current is untouched during validation.
- Choose an exit status in the typed catch, then print the current state once.
Write and run your lab
Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.
Predict the output, edit the source, then select Run code.
Your lab evidence and notes
Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.
Loading local notes…
Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.
Reveal the explained lab solution
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
Expected output
Accepted
State: 0 20
Input syntax is checked before the operation. A valid integer can still be rejected by the reading range rule.
The operation validates its own candidate before swapping. An exception from validation cannot partly replace current.
The handler reports a real failure and uses status two; it does not swallow the error or pretend that catch performed an undo.
Review before moving on
Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.
- Read try, throw and catch as a clear failure path.
- Catch a standard exception by const reference.
- Trace cleanup before the matching handler runs.
- Explain why catch does not undo earlier changes.
- Validate replacement data before changing current state.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.