SovranCode
Modern C++ Lesson 37
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ optional and variant Explained
NEXT LESSONC++ File Handling: Read and Write Text Files
C++ foundations · Lesson 37 About 47 min with practice

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.

Before you begin

Know functions, vectors, object lifetime and RAII. Review optional for an expected missing result; not every alternative needs an exception.

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.

A replacement containing an invalid negative reading is rejected before commit. Scoped work is cleaned up before the failure handler reports the problem. The original reading ten remains unchanged because it was never replaced.
A handler reports failure; it does not automatically undo changes. The old data stays valid here because validation happens before commit, while RAII handles cleanup of completed local objects.
C++ PRACTICE RUNNER

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.

#include <iostream>
#include <stdexcept>
void requireCount(int count) {
    if (count < 0) throw std::invalid_argument("Count must not be negative");
}
int main() {
    try {
        requireCount(-1);
        std::cout << "Accepted\n";
    } catch (const std::invalid_argument& error) {
        std::cout << "Rejected: " << error.what() << '\n';
    }
    std::cout << "Finished\n";
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.
C++ PRACTICE RUNNER

Watch cleanup happen before catch

The local guard is fully constructed before the throw. Its destructor runs as that scope is left.

#include <iostream>
#include <stdexcept>
struct Guard {
    ~Guard() { std::cout << "Cleaned up\n"; }
};
void work() {
    Guard guard;
    throw std::runtime_error("Work failed");
}
int main() {
    try { work(); }
    catch (const std::runtime_error&) { std::cout << "Handled failure\n"; }
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

C++ PRACTICE RUNNER

Reject a replacement without changing the old data

The replacement is checked first. swap is reached only for an accepted candidate.

#include <iostream>
#include <stdexcept>
#include <vector>
void replaceReadings(std::vector<int>& current, std::vector<int> candidate) {
    for (int value : candidate) {
        if (value < 0 || value > 20) throw std::out_of_range("Reading out of range");
    }
    current.swap(candidate);
}
int main() {
    std::vector<int> current{10};
    try { replaceReadings(current, {3, -1}); }
    catch (const std::out_of_range&) { std::cout << "Rejected\n"; }
    std::cout << "Current: " << current.front() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

LOCAL TERMINAL COMMANDS

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.exe
APPLY THE CONCEPT

Independent 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?
  1. Collect input in a separate candidate vector.
  2. Take candidate by value so current is untouched during validation.
  3. Choose an exit status in the typed catch, then print the current state once.
C++ PRACTICE RUNNER

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.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <stdexcept>
#include <vector>
#include <utility>
void replaceReadings(std::vector<int>& current, std::vector<int> candidate) {
    for (int value : candidate) {
        if (value < 0 || value > 20) throw std::out_of_range("Reading out of range");
    }
    current.swap(candidate);
}
int main() {
    int count{};
    if (!readInteger(count) || count < 0 || count > 5) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::vector<int> candidate;
    for (int i = 0; i < count; ++i) {
        int value{};
        if (!readInteger(value)) { std::cout << "Invalid input\n"; return 1; }
        candidate.push_back(value);
    }
    std::vector<int> current{10};
    int status{};
    try {
        replaceReadings(current, std::move(candidate));
        std::cout << "Accepted\n";
    } catch (const std::out_of_range& error) {
        std::cout << "Rejected: " << error.what() << '\n'; status = 2;
    }
    std::cout << "State:";
    for (int value : current) std::cout << ' ' << value;
    std::cout << '\n';
    return status;
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

  • C++ draft: exception handling
  • C++ Core Guidelines: error handling
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01Where does execution continue after a handled throw?
02Does catch automatically undo a changed vector?
03How should this lesson catch a standard exception for reading?
04What runs before the matching handler in the guard example?
05When should current be swapped with candidate?
PREVIOUS LESSONC++ optional and variant Explained
NEXT LESSONC++ File Handling: Read and Write Text Files
ON THIS PAGELearning objectivesAn exception reports that an operation could not finishThe success line is skipped when the call throwsCleanup is not the same as undoBuild and check the new state before replacing the old stateKeep one trusted state while testing a replacementIndependent labReviewKnowledge checkReferences
Course overview