C++ Exception Handling: Try, Catch, and Throw Explained

C++ Exception Handling: Try, Catch, and Throw Explained



—ARTICLE START—

In this beginner-friendly guide, you’ll learn C++ exception handling step by step: C++ try catch throw explained for beginners. By the end, you’ll know what exceptions are, why we use them, and how to write simple, safe code that handles errors gracefully. If you’re new to our C++ series, you can also explore more tutorials in the CodDesire C++ section.

What is exception handling in C++ and why is it used?

C++ try catch throw explained for beginners: how an exception travels from try to catch through the call stack
See how a thrown exception bubbles up the stack to a matching catch handler.

Exception handling is how C++ reports and handles errors at runtime. Instead of returning an error code (like -1) from a function, the function can throw an object (an exception). The runtime then looks for a matching catch block to handle it. This separates normal code from error-handling code, which makes programs clearer and more reliable.

Why use exceptions?

  • They keep the main logic clean by moving error handling into catch blocks.
  • They automatically unwind the stack (local objects get destroyed) so resources are not leaked when errors happen.
  • They can carry helpful messages and types (e.g., std::runtime_error, std::out_of_range).

Modern best practices to remember:

  • Throw by value; catch by const reference.
  • Prefer types derived from std::exception (e.g., std::invalid_argument, std::runtime_error).
  • Only use exceptions for exceptional situations, not routine control flow.

Basic syntax: try, catch, and throw

C++ try catch throw explained for beginners: RAII destructor cleanup during stack unwinding after an exception
RAII releases files, memory, and locks as exceptions unwind C++ stack frames.

Syntax overview for how to use try catch in C++:

  • try { /* code that may fail */ } starts a protected block.
  • throw creates and sends an exception.
  • catch (const SomeType& e) { /* handle */ } handles a matching exception type.

Try → Throw → Catch: how control flows
1) Run risky code inside try { ... }
2) Detect a problem and throw an exception object
3) Stack unwinding: local objects are destroyed (RAII)
4) Nearest matching catch handles the error
5) Optionally throw; to rethrow, or continue after the handler

Order handlers from most specific to most general; put catch (...) last.
Code
#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>

double safe_divide(double a, double b) {
    if (b == 0.0) {
        throw std::runtime_error("division by zero");
    }
    return a / b;
}

int main() {
    try {
        std::cout << safe_divide(10, 2) << "n";
        std::cout << safe_divide(5, 0) << "n"; // throws
        std::cout << "This line will not run after exceptionn";
    } catch (const std::runtime_error& e) {
        std::cout << "Caught runtime_error: " << e.what() << "n";
    } catch (const std::exception& e) { // fallback for std::exception types
        std::cout << "Caught std::exception: " << e.what() << "n";
    }

    std::cout << "Program continuesn";
}
Output
5
Caught runtime_error: division by zero
Program continues

Simple C++ try catch example for students

Here is a simple, readable example that throws and catches an index error. This is a beginner friendly C++ exception handling tutorial pattern you’ll use a lot.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <vector>
#class="cd-package">include <stdexcept>

int get_score(const std::vector<int>& scores, std::size_t i) {
    if (i >= scores.size()) {
        throw std::out_of_range("index out of bounds");
    }
    return scores.at(i); // safe now
}

int main() {
    std::vector<int> scores = {85, 90, 78};

    try {
        std::cout << "Second: " << get_score(scores, 1) << "n";
        std::cout << "Fifth: " << get_score(scores, 4) << "n"; // throws
    } catch (const std::out_of_range& e) {
        std::cout << "Out of range: " << e.what() << "n";
    }
}
Output
Second: 90
Out of range: index out of bounds

Step-by-step guide to throwing and catching exceptions in C++

  1. Write the normal code inside a try block.
  2. Throw when you detect an error: throw std::runtime_error("message");
  3. Catch the most specific exceptions first, then more general ones.
  4. Optionally use catch (...) as a last resort to catch anything.
Code
#class="cd-package">include <iostream>
#class="cd-package">include <string>
#class="cd-package">include <stdexcept>

int main() {
    const std::string inputs[] = {"20", "abc", "9999999999999"};

    for (const auto& s : inputs) {
        try {
            int age = std::stoi(s); // may throw invalid_argument or out_of_range
            std::cout << "Parsed age: " << age << "n";
        } catch (const std::out_of_range&) { // more specific
            std::cout << "Number too large to fit in intn";
        } catch (const std::invalid_argument&) { // then less specific
            std::cout << "Input was not a numbern";
        } catch (...) { // always put this last
            std::cout << "Unknown parsing errorn";
        }
    }
}
Output
Parsed age: 20
Input was not a number
Number too large to fit in int

How the throw keyword works (C++ throw keyword example)

The throw keyword creates an exception object and transfers control to the nearest matching catch. When you need to add context and still let a higher level handle it, rethrow the current exception with plain throw; (not throw e;).

Code
#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>
#class="cd-package">include <string>

void process(const std::string& s) {
    try {
        int n = std::stoi(s);
        if (n <= 0) {
            throw std::runtime_error("value must be > 0");
        }
        std::cout << "OK: " << n << "n";
    } catch (const std::exception&) {
        std::cout << "process() saw an error, rethrowing...n";
        throw; // rethrow preserves the original exception type and message
    }
}

int main() {
    try {
        process("0"); // will cause runtime_error
    } catch (const std::exception& e) {
        std::cout << "Main caught: " << e.what() << "n";
    }
}
Output
process() saw an error, rethrowing...
Main caught: value must be > 0

How to make a custom exception class in C++

You can define your own exception types to make error handling clearer. Derive from std::exception (commonly std::runtime_error) so you can reuse what() and integrate well with existing code.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>
#class="cd-package">include <string>

class NegativeBalanceError : class="cd-keyword cd-access">public std::runtime_error {
class="cd-keyword cd-access">public:
    explicit NegativeBalanceError(const std::string& msg)
        : std::runtime_error(msg) {}
};

int main() {
    double balance = 100.0;
    double withdraw = 120.0;

    try {
        if (withdraw > balance) {
            throw NegativeBalanceError("withdrawal exceeds balance");
        }
        balance -= withdraw;
        std::cout << "New balance: " << balance << "n";
    } catch (const NegativeBalanceError& e) {
        std::cout << "Bank error: " << e.what() << "n";
    } catch (const std::exception& e) {
        std::cout << "General error: " << e.what() << "n";
    }
}
Output
Bank error: withdrawal exceeds balance

C++ exceptions vs return codes

Beginners often ask whether to use exceptions or return codes. Here’s a quick comparison:

Aspect Exceptions Return codes
Readability Separates error handling from normal logic Error checks clutter the main flow
Propagation Automatic via stack unwinding; RAII-friendly Manual propagation needed; easy to forget
Performance No cost on the non-throw path in common ABIs; throwing is expensive Constant overhead on every call
Use case Exceptional, unexpected failures Expected outcomes, simple validation, hot paths
Interoperability Requires exceptions enabled in the toolchain Works even if exceptions are disabled

Qualitative cost profile (normal path vs when errors occur)
Relative only; real costs depend on compiler/ABI and workload. Measure if performance is critical.
Exceptions
Normal path

On error (throw)

Return codes
Normal path

On error

std::optional / std::expected
Normal path

On error

Common mistakes with try catch in C++ and how to fix them

  • Catching by value (risk of slicing)
    • Mistake: catch (std::exception e)
    • Fix: catch (const std::exception& e) — keeps the dynamic type and avoids copying.
  • Rethrowing with throw e;
    • Mistake: catch (...) { throw e; } — may change the exception type.
    • Fix: use plain throw; to preserve the current exception.
  • Catching base before derived
    • Mistake: catching std::exception before std::out_of_range makes the later catch unreachable.
    • Fix: order from most specific to most general; place catch (...) last.
  • Throwing from destructors
    • Mistake: letting exceptions escape a destructor, especially during stack unwinding, leads to std::terminate.
    • Fix: handle errors inside destructors (log, swallow), or expose a non-destructor cleanup function that can fail.
  • Using removed dynamic exception specifications
    • Mistake: void f() throw(std::bad_alloc); — removed in C++17.
    • Fix: use noexcept to state non-throwing functions when appropriate.

Modern tips for beginners

  • Throw by value, catch by const reference — safe and efficient.
  • Use standard exception types like std::invalid_argument, std::range_error, std::out_of_range, std::runtime_error.
  • noexcept: mark functions noexcept when they can’t throw. If an exception escapes a noexcept function, the program calls std::terminate.
  • Don’t overuse exceptions: prefer return codes or types like std::optional or (in C++23) std::expected<T,E> when failures are common and part of normal control flow.
  • Unwinding awareness: during exception unwinding, local objects are destroyed in reverse order. This is why RAII works well with exceptions.
  • Detecting unwinding: if you must, use std::uncaught_exceptions() (replaces removed std::uncaught_exception) to detect if stack unwinding is in progress.
  • Cross-thread errors: to pass exceptions between threads, use std::exception_ptr, std::current_exception(), and std::rethrow_exception().

Beginner try/catch checklist
  • Wrap only the smallest risky section in try { ... } (keep scope tight).
  • Throw by value with a clear message: throw std::runtime_error("why it failed");
  • Catch by const reference: catch (const std::runtime_error& e)
  • Order handlers: specific → general → catch (...) last.
  • Need to add context but defer handling? Log and use throw; to rethrow.
  • Let RAII manage cleanup; never let exceptions escape a destructor.
  • Use noexcept for functions that must not throw.
  • For frequent, expected failures, consider std::optional/std::expected instead of exceptions.

Mini practice: try it yourself

  1. Write a function read_percentage() that throws std::out_of_range if the value is not in [0, 100]. Catch and print a friendly message.
  2. Create a custom exception FileOpenError derived from std::runtime_error. Throw it when simulating a failed open; catch it and print the file path.
  3. Modify one of the examples to add a logging catch that rethrows using plain throw; and handle it in main().

FAQ: C++ try catch throw explained for beginners

What is exception handling in C++ and why is it used?

It’s a structured way to report runtime errors using throw and handle them with catch blocks. It improves clarity, avoids scattered error checks, and works well with RAII to prevent resource leaks.

How do try, catch, and throw work in C++ with a simple example?

Put risky code in a try block, use throw when a problem occurs, and handle it in a matching catch. See the “Basic syntax” and “Simple example” sections above for runnable demos.

What happens if no catch block matches an exception in C++?

The runtime keeps unwinding to outer try blocks. If no handler is found (including in main()), the program calls std::terminate and usually aborts.

Should beginners use exceptions or return codes in C++?

Use exceptions for unexpected, exceptional errors. Use return codes (or types like std::optional/std::expected) for common, expected outcomes or performance-critical paths.

How do I create and use a custom exception class in C++?

Derive from std::exception (often std::runtime_error) and pass a message to the base constructor. Throw your type and catch by const reference. See the “custom exception class” example above.

Common patterns to remember

  • Always order catch blocks from most-specific to most-general.
  • Use catch (...) sparingly and only last.
  • When adding context but letting others handle the error, log and rethrow with throw;.
  • Avoid throwing from destructors; handle failures internally or provide explicit cleanup functions.

Sources / Further reading

Keep practicing and revisit this page whenever you need a refresher on C++ error handling for beginners. For more step-by-step C++ lessons, return to our C++ tutorials hub.

—ARTICLE END—

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted