C++ Exception Handling Complete Guide
Error Safety

C++ Exception Handling: Complete Guide with Examples

Master C++ exception handling: try-catch blocks, exception specifications, RAII pattern, and exception safety guarantees. Learn with practical examples and best practices.

try Block

Protected Code

catch Block

Exception Handler

throw

Raise Exception

noexcept

No Exceptions

C++ Tutorial · Exception Handling

Exceptions separate error paths from normal logic. Learn how to throw and catch, use standard exception types, and pair exceptions with RAII for leak-free cleanup.

What you will learn

  • Structure try/catch blocks for error recovery
  • Throw and catch by const reference
  • Use standard library exception hierarchy
  • Apply RAII so destructors run during stack unwind
  • Document noexcept where functions never throw

Why this topic matters

Exception safety is asked in senior interviews. Companies expect you to avoid bare new and to understand stack unwinding.

Key terms & indexing

C++ exceptions try catch C++ throw C++ RAII exceptions

Introduction to Exception Handling

Exception handling is a mechanism that allows programs to deal with unexpected situations (exceptions) during execution. It provides a structured way to separate error-handling code from normal program logic.

Why Use Exception Handling?
  • Separates error handling from normal code
  • Propagates errors up the call stack
  • Provides clean-up via destructors (RAII)
  • Makes code more robust and maintainable
  • Handles errors that can't be handled locally
  • Supports construction of error hierarchies
Exception Handling Components
  • try: Marks block of code to monitor
  • catch: Handles specific exceptions
  • throw: Raises an exception
  • noexcept: Specifies no exceptions
  • RAII: Resource cleanup pattern
  • Stack Unwinding: Automatic cleanup
Exception Handling Flow
Normal Execution: Code runs in try block
Exception Thrown: throw statement executed
Stack Unwinding: Destructors called for local objects
Catch Handler: Matching catch block executes
Continue Execution: After catch block or terminate

1. Basic Exception Handling: try, catch, throw

The fundamental exception handling mechanism in C++ consists of three keywords: try, catch, and throw.

Basic Syntax
try {
    // Code that might throw exceptions
    if (error_condition) {
        throw exception_object;
    }
}
catch (exception_type& e) {
    // Handle exception
}

Examples

1. try/catch int
try {
    throw 42;
} catch (int e) {
    cout << e;
}
2. catch by reference
catch (const exception& e) {
    cout << e.what();
}
3. throw string
throw string("Invalid input");
4. Multiple catch
catch (int e) { }
catch (double e) { }
catch (...) { }
5. Nested try
try {
    try { throw 1; }
    catch (int e) { throw; }
} catch (...) {}
6. Rethrow
catch (exception& e) {
    cerr << e.what();
    throw;
}
Exception Handling Best Practices:
  • Throw by value, catch by const reference
  • Order catch blocks from specific to general
  • Always handle exceptions at appropriate level
  • Use RAII for resource management
  • Avoid throwing exceptions from destructors
  • Document exception guarantees
Common Mistakes:
  • Catching exceptions by value (slicing)
  • Empty catch blocks (swallowing exceptions)
  • Throwing exceptions from destructors
  • Incorrect catch block order
  • Memory leaks in exception paths
  • Exception safety violations

2. Standard Exception Classes

C++ provides a hierarchy of standard exception classes in the <stdexcept> header. These classes represent common error conditions.

Standard Exception Hierarchy
Logic Errors (Preventable)
  • logic_error
  • invalid_argument
  • domain_error
  • length_error
  • out_of_range
  • future_error (C++11)
Runtime Errors (Unavoidable)
  • runtime_error
  • range_error
  • overflow_error
  • underflow_error
  • system_error (C++11)

Examples

1. invalid_argument
throw invalid_argument("bad age");
2. out_of_range
vector v={1};
cout << v.at(5);
3. runtime_error
if (!file) throw runtime_error("open failed");
4. logic_error
throw logic_error("precondition failed");
5. bad_alloc
int* p = new int[1e9];  // may throw
6. Custom exception
class MyError : public exception {
    const char* what() const noexcept override { return "My"; }
};

Choosing the Right Exception Type

Use logic_error for preventable programming errors. Use runtime_error for errors that could occur despite correct program logic. Always prefer standard exceptions over custom ones when they fit.

3. Exception Specifications and noexcept

Exception specifications declare what exceptions a function might throw. noexcept (C++11) indicates a function won't throw exceptions.

Exception Specification Syntax
Dynamic Exception Specifications (Deprecated)
// C++98 style (deprecated)
void func() throw(int, std::runtime_error);
void func() throw();  // No exceptions
noexcept (C++11)
// Modern C++ style
void func() noexcept;      // Won't throw
void func() noexcept(true);
void func() noexcept(false);

Examples

1. noexcept true
void swap(int& a,int& b) noexcept {
    int t=a; a=b; b=t;
}
2. noexcept(false)
void mayFail() noexcept(false) { throw 1; }
3. noexcept operator
cout << noexcept(func());
4. throw() deprecated
// void f() throw();  // old style
5. noexcept move
vector(vector&&) noexcept;
6. Conditional noexcept
template
void f() noexcept(noexcept(T()));
noexcept (C++11+)
  • Compile-time specification
  • Better performance optimization
  • Calls std::terminate() on violation
  • Can be conditional
  • Recommended for modern C++
throw() (Deprecated)
  • Runtime checking (deprecated)
  • Performance overhead
  • Calls unexpected() then terminate()
  • Removed in C++17
  • Avoid in new code
No Specification
  • Function may throw any exception
  • Most flexible but least informative
  • No performance penalty for checking
  • Use when exception behavior varies
  • Document exception behavior in comments

4. RAII and Exception Safety

RAII (Resource Acquisition Is Initialization) is a fundamental C++ idiom that ties resource management to object lifetime, ensuring proper cleanup even when exceptions occur.

RAII Principle
class Resource {
    Handle* handle;
public:
    Resource() : handle(acquire_resource()) {
        // Resource acquired in constructor
    }
    
    ~Resource() {
        release_resource(handle);  // Resource released in destructor
    }
    
    // Disable copying (or implement properly)
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
};

Examples

1. lock_guard
mutex m;
lock_guard lock(m);
// unlocks even if throw
2. unique_ptr RAII
auto p = make_unique(5);
3. fstream RAII
ifstream in("data.txt");
// closes on scope exit
4. vector strong guarantee
vector backup = v;
try { v.push_back(x); }
catch(...) { v=backup; throw; }
5. Rule of zero
class User {
    string name;
    vector scores;
};
6. Destructor cleanup
~File() { if (fp) fclose(fp); }
Exception Safety Rules:
  • Never throw from destructors - can cause std::terminate()
  • Use RAII for all resource management
  • Provide at least basic exception safety guarantee
  • Aim for strong guarantee for key operations
  • Use copy-and-swap for assignment operators
  • Make swap operations noexcept
  • Document exception safety guarantees in comments

5. Advanced Exception Handling Techniques

Advanced exception handling patterns including nested exceptions, exception propagation, and handling strategies for large systems.

Examples

1. exception_ptr
exception_ptr ep = current_exception();
rethrow_exception(ep);
2. nested_exception
throw nested_exception();
3. set_terminate
set_terminate([]{ cerr << "terminate"; abort(); });
4. noexcept stack unwinding
void g() noexcept { /* no throw */ }
5. try in constructor
try { init(); } catch(...) { cleanup(); throw; }
6. Function try block
Foo() try : x(0) {} catch(...) { throw; }
Choosing Exception Handling Strategy:
  • Exceptions: For unexpected, recoverable errors across multiple layers
  • Error codes: For expected failures in performance-critical code
  • std::optional: When "no value" is a valid, expected result
  • Assertions: For programming errors that should never occur
  • Termination: For unrecoverable errors where continuing is dangerous
  • Logging: Always log exceptions before handling (for debugging)

6. Exception Handling Best Practices

Best Practices
  • Throw by value, catch by const reference
  • Use RAII for all resource management
  • Mark destructors as noexcept
  • Provide at least basic exception safety
  • Document exception guarantees
  • Use standard exceptions when appropriate
  • Catch exceptions at appropriate level
Common Mistakes
  • Empty catch blocks (swallowing exceptions)
  • Throwing exceptions from destructors
  • Catching exceptions by value (object slicing)
  • Using exceptions for normal control flow
  • Not cleaning up resources in exception paths
  • Incorrect catch block order
  • Forgetting to mark move operations as noexcept
Critical Rules (Never Violate)
  • NEVER throw from destructors - causes std::terminate()
  • NEVER let exceptions escape noexcept functions - causes std::terminate()
  • ALWAYS provide basic exception safety - no resource leaks
  • ALWAYS use RAII for resource management
  • NEVER use exceptions across module/DLL boundaries unless explicitly designed for it

Examples

1. Catch specific
catch (const invalid_argument& e) {}
2. Catch by const ref
catch (const exception& e) { log(e.what()); }
3. Do not catch all silently
catch (...) { /* log and rethrow or handle */ }
4. Use RAII
unique_ptr p = make_unique();
5. Bad: catch(...){}
catch (...) { }  // hides bugs
6. Bad: throw int for logic
if (bad) throw 1;  // prefer exception types

Exception Safety Guarantees Comparison

The following table compares different exception safety guarantees with their characteristics and implementation requirements:

Safety Level Guarantee Characteristics Implementation Requirements
No-throw Guarantee Operation never throws exceptions Strongest guarantee, marked with noexcept All operations must be non-throwing
Strong Guarantee Commit-or-rollback semantics Operation succeeds completely or has no effect Copy-and-swap idiom, operations on temporaries
Basic Guarantee No resource leaks, valid state Objects remain valid but unspecified state RAII for resources, destructor cleanup
No Guarantee No promises Resource leaks possible, invalid state possible Avoid! Never acceptable in production code
Aiming for Exception Safety:
  • All functions should provide at least basic guarantee
  • Destructors, move operations, and swap should be noexcept
  • Key operations should provide strong guarantee
  • Document the guarantee level for each function
  • Test exception safety with exception injection

Frequently asked questions

Should destructors throw?

No—if a destructor throws during unwind, std::terminate is called.

What is exception safety guarantee?

Basic: no leaks after exception; strong: state unchanged if throw; noexcept: never throws.

Catch by reference or value?

Catch const std::exception& to avoid slicing polymorphic exception objects.