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

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
}
Basic Exception Handling Examples
#include <iostream>
#include <string>
#include <stdexcept>
#include <cmath>
using namespace std;

void demonstrateBasicExceptions() {
    cout << "=== Basic Exception Handling ===" << endl;
    
    // Example 1: Simple exception handling
    try {
        cout << "Enter a positive number: ";
        int num;
        cin >> num;
        
        if (num <= 0) {
            throw runtime_error("Number must be positive!");
        }
        
        cout << "You entered: " << num << endl;
    }
    catch (const runtime_error& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    // Example 2: Multiple catch blocks
    try {
        cout << "\nEnter two numbers (divisor can't be zero): ";
        int a, b;
        cin >> a >> b;
        
        if (b == 0) {
            throw "Division by zero!";  // Throwing string literal
        }
        
        double result = static_cast<double>(a) / b;
        cout << "Result: " << result << endl;
    }
    catch (const char* error_msg) {
        cout << "Caught string error: " << error_msg << endl;
    }
    catch (const exception& e) {
        cout << "Caught standard exception: " << e.what() << endl;
    }
    catch (...) {  // Catch-all handler
        cout << "Caught unknown exception!" << endl;
    }
    
    // Example 3: Nested try-catch blocks
    try {
        cout << "\nOuter try block" << endl;
        
        try {
            cout << "Inner try block" << endl;
            throw invalid_argument("Invalid argument from inner block");
        }
        catch (const invalid_argument& e) {
            cout << "Inner catch: " << e.what() << endl;
            // Can rethrow exception
            throw;  // Rethrow to outer handler
        }
    }
    catch (const exception& e) {
        cout << "Outer catch: " << e.what() << endl;
    }
    
    // Example 4: Exception in function
    auto divide = [](int numerator, int denominator) -> double {
        if (denominator == 0) {
            throw domain_error("Cannot divide by zero");
        }
        return static_cast<double>(numerator) / denominator;
    };
    
    try {
        cout << "\nDivision function test:" << endl;
        cout << "10 / 2 = " << divide(10, 2) << endl;
        cout << "5 / 0 = " << divide(5, 0) << endl;  // Will throw
    }
    catch (const domain_error& e) {
        cout << "Division error: " << e.what() << endl;
    }
    
    // Example 5: Custom exception class
    class NegativeNumberException : public exception {
    private:
        string message;
    public:
        NegativeNumberException(const string& msg) : message(msg) {}
        
        const char* what() const noexcept override {
            return message.c_str();
        }
    };
    
    auto squareRoot = [](double x) -> double {
        if (x < 0) {
            throw NegativeNumberException("Cannot calculate square root of negative number");
        }
        return sqrt(x);
    };
    
    try {
        cout << "\nSquare root function test:" << endl;
        cout << "sqrt(16) = " << squareRoot(16) << endl;
        cout << "sqrt(-4) = " << squareRoot(-4) << endl;  // Will throw
    }
    catch (const NegativeNumberException& e) {
        cout << "Custom exception: " << e.what() << endl;
    }
    
    // Example 6: Exception propagation
    cout << "\n=== Exception Propagation ===" << endl;
    
    void functionC() {
        cout << "In functionC, throwing exception..." << endl;
        throw logic_error("Error from functionC");
    }
    
    void functionB() {
        cout << "In functionB, calling functionC..." << endl;
        functionC();
        cout << "This won't be printed" << endl;
    }
    
    void functionA() {
        cout << "In functionA, calling functionB..." << endl;
        functionB();
        cout << "This won't be printed either" << endl;
    }
    
    try {
        functionA();
    }
    catch (const logic_error& e) {
        cout << "Caught in main: " << e.what() << endl;
        cout << "Exception propagated through functionA -> functionB -> functionC" << endl;
    }
    
    cout << endl;
}

void demonstrateExceptionObjects() {
    cout << "=== Exception Objects and Information ===" << endl;
    
    // Example 1: Standard exception hierarchy
    try {
        vector<int> v(5);
        cout << "Accessing element at index 10..." << endl;
        cout << v.at(10) << endl;  // Throws out_of_range
    }
    catch (const out_of_range& e) {
        cout << "Out of range error: " << e.what() << endl;
    }
    catch (const exception& e) {
        cout << "Standard exception: " << e.what() << endl;
    }
    
    // Example 2: Exception with additional information
    class FileNotFoundException : public exception {
    private:
        string filename;
        string full_message;
    public:
        FileNotFoundException(const string& fname) 
            : filename(fname), 
              full_message("File not found: " + fname) {}
        
        const char* what() const noexcept override {
            return full_message.c_str();
        }
        
        const string& getFilename() const { return filename; }
    };
    
    try {
        throw FileNotFoundException("data.txt");
    }
    catch (const FileNotFoundException& e) {
        cout << "\nFile error: " << e.what() << endl;
        cout << "Missing file: " << e.getFilename() << endl;
    }
    
    // Example 3: Polymorphic exception handling
    try {
        // Can throw different exception types
        throw bad_alloc();  // Memory allocation failure
    }
    catch (const bad_alloc& e) {
        cout << "\nMemory allocation failed: " << e.what() << endl;
    }
    catch (const bad_cast& e) {
        cout << "\nBad cast: " << e.what() << endl;
    }
    catch (const exception& e) {
        cout << "\nGeneric exception: " << e.what() << endl;
    }
    
    cout << endl;
}

void demonstrateCatchOrder() {
    cout << "=== Catch Block Order ===" << endl;
    
    // Important: Catch more specific exceptions first!
    
    // WRONG ORDER:
    try {
        throw runtime_error("Test exception");
    }
    catch (const exception& e) {  // Too general - catches everything
        cout << "Caught in generic exception handler" << endl;
    }
    catch (const runtime_error& e) {  // Never reached!
        cout << "This will never execute" << endl;
    }
    
    // CORRECT ORDER:
    try {
        throw runtime_error("Test exception");
    }
    catch (const runtime_error& e) {  // Specific handler first
        cout << "Caught runtime_error: " << e.what() << endl;
    }
    catch (const exception& e) {  // Generic handler last
        cout << "Caught generic exception: " << e.what() << endl;
    }
    
    // Catch-all handler (...) should always be last
    try {
        throw "String exception";
    }
    catch (const runtime_error& e) {
        cout << "Runtime error" << endl;
    }
    catch (const exception& e) {
        cout << "Standard exception" << endl;
    }
    catch (...) {  // Catch-all must be last
        cout << "Caught unknown exception type" << endl;
    }
    
    cout << endl;
}

int main() {
    demonstrateBasicExceptions();
    demonstrateExceptionObjects();
    demonstrateCatchOrder();
    
    return 0;
}
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)
Standard Exception Examples
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <limits>
#include <fstream>
#include <memory>
using namespace std;

void demonstrateLogicErrors() {
    cout << "=== Logic Error Exceptions ===" << endl;
    
    // 1. invalid_argument - invalid parameter value
    try {
        auto calculateFactorial = [](int n) -> long long {
            if (n < 0) {
                throw invalid_argument("Factorial not defined for negative numbers");
            }
            if (n > 20) {
                throw invalid_argument("Factorial too large for long long");
            }
            
            long long result = 1;
            for (int i = 2; i <= n; ++i) {
                result *= i;
            }
            return result;
        };
        
        cout << "5! = " << calculateFactorial(5) << endl;
        cout << "-3! = " << calculateFactorial(-3) << endl;  // Throws
    }
    catch (const invalid_argument& e) {
        cout << "Invalid argument: " << e.what() << endl;
    }
    
    // 2. domain_error - domain violation
    try {
        auto safeDivide = [](double a, double b) -> double {
            if (b == 0.0) {
                throw domain_error("Division by zero");
            }
            return a / b;
        };
        
        cout << "\n10 / 2 = " << safeDivide(10, 2) << endl;
        cout << "5 / 0 = " << safeDivide(5, 0) << endl;  // Throws
    }
    catch (const domain_error& e) {
        cout << "Domain error: " << e.what() << endl;
    }
    
    // 3. length_error - exceeds maximum size
    try {
        vector<int> v;
        v.reserve(v.max_size() - 1);
        cout << "\nAttempting to reserve too much memory..." << endl;
        v.reserve(v.max_size() + 1);  // Throws length_error
    }
    catch (const length_error& e) {
        cout << "Length error: " << e.what() << endl;
    }
    
    // 4. out_of_range - index out of bounds
    try {
        vector<int> numbers = {1, 2, 3, 4, 5};
        cout << "\nAccessing vector elements:" << endl;
        cout << "numbers[2] = " << numbers[2] << endl;
        cout << "numbers.at(10) = " << numbers.at(10) << endl;  // Throws
    }
    catch (const out_of_range& e) {
        cout << "Out of range: " << e.what() << endl;
    }
    
    // 5. logic_error - general logic error
    try {
        auto validateAge = [](int age) {
            if (age < 0 || age > 150) {
                throw logic_error("Invalid age value");
            }
            return age;
        };
        
        cout << "\nAge 25: " << validateAge(25) << endl;
        cout << "Age -5: " << validateAge(-5) << endl;  // Throws
    }
    catch (const logic_error& e) {
        cout << "Logic error: " << e.what() << endl;
    }
    
    cout << endl;
}

void demonstrateRuntimeErrors() {
    cout << "=== Runtime Error Exceptions ===" << endl;
    
    // 1. range_error - result outside valid range
    try {
        auto calculatePercentage = [](double part, double whole) -> double {
            if (whole == 0.0) {
                throw runtime_error("Whole cannot be zero");
            }
            
            double percentage = (part / whole) * 100.0;
            
            if (percentage < 0.0 || percentage > 100.0) {
                throw range_error("Percentage out of valid range [0, 100]");
            }
            
            return percentage;
        };
        
        cout << "50 of 200 = " << calculatePercentage(50, 200) << "%" << endl;
        cout << "150 of 100 = " << calculatePercentage(150, 100) << "%" << endl;  // Throws
    }
    catch (const range_error& e) {
        cout << "Range error: " << e.what() << endl;
    }
    catch (const runtime_error& e) {
        cout << "Runtime error: " << e.what() << endl;
    }
    
    // 2. overflow_error - arithmetic overflow
    try {
        auto safeAdd = [](int a, int b) -> int {
            if ((b > 0) && (a > numeric_limits<int>::max() - b)) {
                throw overflow_error("Integer overflow in addition");
            }
            if ((b < 0) && (a < numeric_limits<int>::min() - b)) {
                throw overflow_error("Integer underflow in addition");
            }
            return a + b;
        };
        
        cout << "\n100 + 200 = " << safeAdd(100, 200) << endl;
        cout << "MAX_INT + 1 = " << safeAdd(numeric_limits<int>::max(), 1) << endl;  // Throws
    }
    catch (const overflow_error& e) {
        cout << "Overflow error: " << e.what() << endl;
    }
    
    // 3. underflow_error - arithmetic underflow
    try {
        auto safeDivideDouble = [](double a, double b) -> double {
            double result = a / b;
            
            // Check for underflow (result too close to zero)
            if (result != 0.0 && abs(result) < numeric_limits<double>::min()) {
                throw underflow_error("Floating point underflow");
            }
            
            return result;
        };
        
        cout << "\n1.0 / 2.0 = " << safeDivideDouble(1.0, 2.0) << endl;
        // Note: Actual underflow is hard to demonstrate without special values
    }
    catch (const underflow_error& e) {
        cout << "Underflow error: " << e.what() << endl;
    }
    
    // 4. system_error (C++11) - system/library errors
    try {
        ifstream file("nonexistent_file.txt");
        file.exceptions(ifstream::failbit);  // Throw on failure
        
        string line;
        getline(file, line);  // Throws system_error if file doesn't exist
    }
    catch (const system_error& e) {
        cout << "\nSystem error: " << e.what() << endl;
        cout << "Error code: " << e.code() << endl;
        cout << "Error category: " << e.code().category().name() << endl;
    }
    
    cout << endl;
}

void demonstrateBadAllocations() {
    cout << "=== Bad Allocation Exceptions ===" << endl;
    
    // bad_alloc - memory allocation failure
    try {
        cout << "Attempting to allocate massive memory..." << endl;
        
        // Try to allocate more memory than available
        size_t huge_size = numeric_limits<size_t>::max() / 2;
        int* massive_array = new int[huge_size];
        
        delete[] massive_array;
    }
    catch (const bad_alloc& e) {
        cout << "Bad allocation: " << e.what() << endl;
        cout << "Memory allocation failed!" << endl;
    }
    
    // bad_array_new_length (C++11) - invalid array length
    try {
        cout << "\nAttempting to create array with negative size..." << endl;
        size_t negative_size = -1;  // Large positive due to wrap-around
        int* bad_array = new int[negative_size];
        
        delete[] bad_array;
    }
    catch (const bad_array_new_length& e) {
        cout << "Bad array new length: " << e.what() << endl;
    }
    
    cout << endl;
}

void demonstrateTypeConversions() {
    cout << "=== Type Conversion Exceptions ===" << endl;
    
    // bad_cast - failed dynamic_cast on references
    try {
        class Base {
        public:
            virtual ~Base() = default;
        };
        
        class Derived : public Base {
        public:
            void specificMethod() {
                cout << "Derived specific method" << endl;
            }
        };
        
        Base base_obj;
        // This will throw bad_cast because base_obj is not a Derived
        Derived& derived_ref = dynamic_cast<Derived&>(base_obj);
        derived_ref.specificMethod();
    }
    catch (const bad_cast& e) {
        cout << "Bad cast: " << e.what() << endl;
        cout << "dynamic_cast failed on reference" << endl;
    }
    
    // bad_typeid - typeid on null pointer
    try {
        class PolyBase {
        public:
            virtual ~PolyBase() = default;
        };
        
        PolyBase* ptr = nullptr;
        // typeid on null pointer of polymorphic type throws bad_typeid
        cout << "Type: " << typeid(*ptr).name() << endl;
    }
    catch (const bad_typeid& e) {
        cout << "\nBad typeid: " << e.what() << endl;
        cout << "typeid called on null pointer of polymorphic type" << endl;
    }
    
    cout << endl;
}

void demonstrateExceptionHierarchy() {
    cout << "=== Exception Class Hierarchy ===" << endl;
    
    // Demonstrating polymorphism in exception handling
    try {
        // Can throw any exception type
        throw out_of_range("Index out of bounds");
    }
    catch (const out_of_range& e) {
        cout << "Caught out_of_range (most specific): " << e.what() << endl;
    }
    catch (const logic_error& e) {
        cout << "Caught logic_error: " << e.what() << endl;
    }
    catch (const exception& e) {
        cout << "Caught exception (most general): " << e.what() << endl;
    }
    
    cout << "\nException class relationships:" << endl;
    cout << "exception (base class)" << endl;
    cout << "├── logic_error" << endl;
    cout << "│   ├── invalid_argument" << endl;
    cout << "│   ├── domain_error" << endl;
    cout << "│   ├── length_error" << endl;
    cout << "│   └── out_of_range" << endl;
    cout << "└── runtime_error" << endl;
    cout << "    ├── range_error" << endl;
    cout << "    ├── overflow_error" << endl;
    cout << "    ├── underflow_error" << endl;
    cout << "    └── system_error (C++11)" << endl;
    cout << endl;
    
    cout << "Other exception types (not derived from std::exception):" << endl;
    cout << "- bad_alloc (memory allocation)" << endl;
    cout << "- bad_cast (dynamic_cast failure)" << endl;
    cout << "- bad_typeid (typeid on null pointer)" << endl;
    cout << "- bad_exception (unexpected exception)" << endl;
    cout << "- bad_array_new_length (C++11)" << endl;
    cout << "- bad_weak_ptr (C++11)" << endl;
    
    cout << endl;
}

int main() {
    demonstrateLogicErrors();
    demonstrateRuntimeErrors();
    demonstrateBadAllocations();
    demonstrateTypeConversions();
    demonstrateExceptionHierarchy();
    
    return 0;
}

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);
Exception Specifications Examples
#include <iostream>
#include <stdexcept>
#include <vector>
#include <type_traits>
#include <string>
using namespace std;

void demonstrateNoexcept() {
    cout << "=== noexcept Specifier (C++11) ===" << endl;
    
    // 1. Basic noexcept usage
    void safeFunction() noexcept {
        cout << "This function promises not to throw exceptions" << endl;
        // All operations here must not throw
    }
    
    void potentiallyThrowingFunction() {
        throw runtime_error("This function might throw");
    }
    
    // 2. noexcept operator - checks if expression is noexcept
    cout << "safeFunction is noexcept? " 
         << (noexcept(safeFunction()) ? "Yes" : "No") << endl;
    
    cout << "potentiallyThrowingFunction is noexcept? " 
         << (noexcept(potentiallyThrowingFunction()) ? "Yes" : "No") << endl;
    
    // 3. Conditional noexcept
    template<typename T>
    void swapValues(T& a, T& b) noexcept(noexcept(T(std::move(a))) && 
                                         noexcept(a.~T())) {
        // This is noexcept if T's move constructor and destructor are noexcept
        T temp(std::move(a));
        a = std::move(b);
        b = std::move(temp);
    }
    
    // 4. noexcept in different contexts
    class NoThrowClass {
    public:
        // Constructor with noexcept
        NoThrowClass() noexcept {
            cout << "NoThrowClass constructor (noexcept)" << endl;
        }
        
        // Destructor should almost always be noexcept
        ~NoThrowClass() noexcept {
            cout << "NoThrowClass destructor (noexcept)" << endl;
        }
        
        // Copy constructor with noexcept
        NoThrowClass(const NoThrowClass&) noexcept {
            cout << "NoThrowClass copy constructor (noexcept)" << endl;
        }
        
        // Move constructor with noexcept
        NoThrowClass(NoThrowClass&&) noexcept {
            cout << "NoThrowClass move constructor (noexcept)" << endl;
        }
    };
    
    class MightThrowClass {
    public:
        MightThrowClass() {
            // Might throw
        }
        
        ~MightThrowClass() {
            // Destructors should not throw!
        }
    };
    
    // 5. noexcept and STL
    vector<NoThrowClass> vec1;
    vec1.push_back(NoThrowClass());  // Efficient with noexcept move
    
    cout << "\nNoexcept checks on standard types:" << endl;
    cout << "int move is noexcept? " << is_nothrow_move_constructible<int>::value << endl;
    cout << "string move is noexcept? " << is_nothrow_move_constructible<string>::value << endl;
    
    // 6. noexcept and optimization
    auto optimizedFunction = [](int x) noexcept -> int {
        // Compiler can optimize better knowing no exceptions
        return x * 2;
    };
    
    cout << "\nOptimized function result: " << optimizedFunction(5) << endl;
    
    // 7. Violating noexcept (calls std::terminate)
    cout << "\nTesting noexcept violation..." << endl;
    void violatingFunction() noexcept {
        throw runtime_error("This violates noexcept!");  // Will call terminate()
    }
    
    try {
        // Uncomment to see terminate() being called
        // violatingFunction();
    }
    catch (...) {
        cout << "Exception caught (but won't happen for noexcept function)" << endl;
    }
    
    cout << endl;
}

void demonstrateDeprecatedExceptionSpecifications() {
    cout << "=== Deprecated Exception Specifications (C++98) ===" << endl;
    
    // Note: These are deprecated in C++11 and removed in C++17
    // They're shown here for educational purposes only
    
    // Dynamic exception specification (deprecated)
    void oldStyleFunction() throw(runtime_error, logic_error) {
        // Can only throw runtime_error or logic_error
        throw runtime_error("Old style exception");
    }
    
    void noThrowOldStyle() throw() {
        // Should not throw any exceptions
        // But check happens at runtime, not compile time
    }
    
    try {
        oldStyleFunction();
    }
    catch (const runtime_error& e) {
        cout << "Caught runtime_error from old-style function: " << e.what() << endl;
    }
    
    cout << "\nWhy dynamic exception specifications are deprecated:" << endl;
    cout << "1. Runtime overhead for checking" << endl;
    cout << "2. Poor interaction with templates" << endl;
    cout << "3. Not enforced at compile time" << endl;
    cout << "4. Calls unexpected() then terminate() on violation" << endl;
    cout << "5. noexcept is better in every way" << endl;
    
    cout << endl;
}

void demonstrateExceptionSpecificationsInPractice() {
    cout << "=== Exception Specifications in Practice ===" << endl;
    
    // 1. When to use noexcept
    cout << "Use noexcept for:" << endl;
    cout << "- Move constructors and move assignment operators" << endl;
    cout << "- Destructors (always!)" << endl;
    cout << "- Swap functions" << endl;
    cout << "- Simple getters and trivial functions" << endl;
    cout << "- Functions that only call other noexcept functions" << endl;
    
    cout << "\n2. Example: Resource management class" << endl;
    class ResourceManager {
    private:
        vector<int> resources;
    public:
        ResourceManager() noexcept = default;
        
        // Destructor must be noexcept
        ~ResourceManager() noexcept {
            // Clean up resources
        }
        
        // Move constructor should be noexcept
        ResourceManager(ResourceManager&& other) noexcept
            : resources(std::move(other.resources)) {}
        
        // Move assignment should be noexcept
        ResourceManager& operator=(ResourceManager&& other) noexcept {
            resources = std::move(other.resources);
            return *this;
        }
        
        // This function might throw
        void addResource(int value) {
            if (value < 0) {
                throw invalid_argument("Resource value cannot be negative");
            }
            resources.push_back(value);
        }
        
        // This function is noexcept
        size_t getResourceCount() const noexcept {
            return resources.size();
        }
    };
    
    // 3. Template programming with noexcept
    template<typename T>
    class SafeContainer {
    private:
        T* data;
        size_t size;
    public:
        // Constructor is noexcept if T's constructor is noexcept
        SafeContainer(size_t n) noexcept(noexcept(T())) 
            : data(new T[n]), size(n) {}
        
        // Destructor is noexcept (must be!)
        ~SafeContainer() noexcept {
            delete[] data;
        }
        
        // Move constructor
        SafeContainer(SafeContainer&& other) noexcept
            : data(other.data), size(other.size) {
            other.data = nullptr;
            other.size = 0;
        }
    };
    
    // 4. noexcept in function pointers
    using NoExceptFunc = void(*)() noexcept;
    using MightThrowFunc = void(*)();
    
    void noThrowFunc() noexcept {
        cout << "No-throw function" << endl;
    }
    
    void mightThrowFunc() {
        cout << "Might-throw function" << endl;
    }
    
    NoExceptFunc ptr1 = noThrowFunc;  // OK
    // NoExceptFunc ptr2 = mightThrowFunc;  // Error: not noexcept
    
    cout << "\n3. Performance implications:" << endl;
    cout << "noexcept allows compilers to:" << endl;
    cout << "- Generate more efficient code" << endl;
    cout << "- Omit exception handling overhead" << endl;
    cout << "- Enable move semantics in more cases" << endl;
    cout << "- Optimize across function boundaries" << endl;
    
    // 5. Testing noexcept at compile time
    cout << "\n4. Compile-time noexcept checking:" << endl;
    
    struct NoThrowType {
        NoThrowType() noexcept = default;
        ~NoThrowType() noexcept = default;
    };
    
    struct MightThrowType {
        MightThrowType() {}  // Might throw
    };
    
    cout << "NoThrowType default constructor is noexcept? "
         << is_nothrow_default_constructible<NoThrowType>::value << endl;
    
    cout << "MightThrowType default constructor is noexcept? "
         << is_nothrow_default_constructible<MightThrowType>::value << endl;
    
    cout << "\n5. Best practices summary:" << endl;
    cout << "- Always mark destructors as noexcept" << endl;
    cout <<- Mark move operations as noexcept when possible" << endl;
    cout << "- Use noexcept for simple functions that can't throw" << endl;
    cout << "- Don't use noexcept(false) unless necessary" << endl;
    cout << "- Consider noexcept as part of your interface contract" << endl;
    
    cout << endl;
}

void demonstrateNoexceptInAlgorithms() {
    cout << "=== noexcept in STL Algorithms ===" << endl;
    
    // STL algorithms can be more efficient with noexcept types
    vector<string> strings = {"apple", "banana", "cherry"};
    
    cout << "Original vector: ";
    for (const auto& s : strings) cout << s << " ";
    cout << endl;
    
    // std::move with noexcept types is more efficient
    vector<string> moved_strings;
    moved_strings.reserve(strings.size());
    
    // If string's move constructor is noexcept, this is efficient
    for (auto& s : strings) {
        moved_strings.push_back(std::move(s));
    }
    
    cout << "After move: strings size = " << strings.size() 
         << ", moved_strings size = " << moved_strings.size() << endl;
    
    // Check noexcept properties of standard types
    cout << "\nStandard type noexcept properties:" << endl;
    cout << "int: " << endl;
    cout << "  Default construct: " << is_nothrow_default_constructible<int>::value << endl;
    cout << "  Copy construct: " << is_nothrow_copy_constructible<int>::value << endl;
    cout << "  Move construct: " << is_nothrow_move_constructible<int>::value << endl;
    
    cout << "\nstring (C++11 and later):" << endl;
    cout << "  Move construct: " << is_nothrow_move_constructible<string>::value << endl;
    cout << "  Move assign: " << is_nothrow_move_assignable<string>::value << endl;
    
    // noexcept and swap
    cout << "\nstd::swap is noexcept if types are nothrow move constructible/assignable" << endl;
    cout << "swap(int, int) is noexcept? " << noexcept(swap(declval<int>(), declval<int>())) << endl;
    
    cout << endl;
}

int main() {
    demonstrateNoexcept();
    demonstrateDeprecatedExceptionSpecifications();
    demonstrateExceptionSpecificationsInPractice();
    demonstrateNoexceptInAlgorithms();
    
    return 0;
}
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;
};
RAII and Exception Safety Examples
#include <iostream>
#include <memory>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <mutex>
using namespace std;

void demonstrateBasicRAII() {
    cout << "=== Basic RAII Examples ===" << endl;
    
    // 1. Manual resource management (BAD)
    cout << "1. Manual resource management (error-prone):" << endl;
    void manualResourceManagement() {
        int* array = new int[100];
        
        try {
            // Some operations that might throw
            throw runtime_error("Something went wrong");
            
            delete[] array;  // This might not be reached!
        }
        catch (...) {
            delete[] array;  // Must remember to cleanup in catch!
            throw;
        }
    }
    
    // 2. RAII with smart pointers (GOOD)
    cout << "\n2. RAII with unique_ptr (automatic cleanup):" << endl;
    void raiiWithSmartPointer() {
        unique_ptr<int[]> array(new int[100]);
        
        // Operations that might throw
        throw runtime_error("Something went wrong");
        
        // No need to manually delete - unique_ptr destructor does it
    }
    
    try {
        raiiWithSmartPointer();
    }
    catch (const exception& e) {
        cout << "Exception caught, but resources were automatically cleaned up!" << endl;
    }
    
    // 3. Custom RAII class for file handling
    cout << "\n3. Custom RAII File Wrapper:" << endl;
    class FileRAII {
    private:
        FILE* file;
    public:
        FileRAII(const char* filename, const char* mode) 
            : file(fopen(filename, mode)) {
            if (!file) {
                throw runtime_error("Failed to open file");
            }
            cout << "File opened: " << filename << endl;
        }
        
        ~FileRAII() {
            if (file) {
                fclose(file);
                cout << "File closed automatically" << endl;
            }
        }
        
        // Delete copy operations
        FileRAII(const FileRAII&) = delete;
        FileRAII& operator=(const FileRAII&) = delete;
        
        // Allow move operations
        FileRAII(FileRAII&& other) noexcept : file(other.file) {
            other.file = nullptr;
        }
        
        FileRAII& operator=(FileRAII&& other) noexcept {
            if (this != &other) {
                if (file) fclose(file);
                file = other.file;
                other.file = nullptr;
            }
            return *this;
        }
        
        FILE* get() const { return file; }
    };
    
    try {
        FileRAII file("test.txt", "w");
        // Use file.get() for operations
        // File automatically closed when 'file' goes out of scope
    }
    catch (const exception& e) {
        cout << "File error: " << e.what() << endl;
    }
    
    cout << endl;
}

void demonstrateExceptionSafetyGuarantees() {
    cout << "=== Exception Safety Guarantees ===" << endl;
    
    // Three levels of exception safety:
    cout << "1. No-throw guarantee (strongest)" << endl;
    cout << "   - Operation never throws exceptions" << endl;
    cout << "   - Marked with noexcept" << endl;
    
    cout << "\n2. Strong guarantee (commit-or-rollback)" << endl;
    cout << "   - Operation either completes successfully" << endl;
    cout << "   - Or has no effect if exception occurs" << endl;
    cout << "   - State unchanged on failure" << endl;
    
    cout << "\n3. Basic guarantee" << endl;
    cout << "   - No resource leaks on exception" << endl;
    cout << "   - Objects remain in valid (but unspecified) state" << endl;
    cout << "   - Program continues execution" << endl;
    
    cout << "\n4. No guarantee (weakest)" << endl;
    cout << "   - Resource leaks possible" << endl;
    cout << "   - Objects may be left in invalid state" << endl;
    cout << "   - Program may crash" << endl;
    
    // Examples of different guarantees
    cout << "\n=== Examples ===" << endl;
    
    // 1. No-throw guarantee example
    class NoThrowStack {
    private:
        vector<int> data;
    public:
        void push(int value) noexcept {
            try {
                data.push_back(value);
            }
            catch (...) {
                // Handle exception internally
                // Still maintains no-throw guarantee
            }
        }
        
        bool empty() const noexcept {
            return data.empty();
        }
    };
    
    // 2. Strong guarantee example
    class Account {
    private:
        double balance;
    public:
        Account(double initial) : balance(initial) {}
        
        // Strong guarantee: either both transfers succeed or neither does
        void transferFunds(Account& to, double amount) {
            if (amount <= 0) {
                throw invalid_argument("Amount must be positive");
            }
            if (amount > balance) {
                throw runtime_error("Insufficient funds");
            }
            
            // Create temporary copies
            double old_from = balance;
            double old_to = to.balance;
            
            try {
                balance -= amount;
                to.balance += amount;
            }
            catch (...) {
                // Rollback on exception
                balance = old_from;
                to.balance = old_to;
                throw;
            }
        }
    };
    
    // 3. Basic guarantee example
    class BasicGuaranteeStack {
    private:
        int* data;
        size_t capacity;
        size_t size;
    public:
        BasicGuaranteeStack() : data(nullptr), capacity(0), size(0) {}
        
        ~BasicGuaranteeStack() {
            delete[] data;
        }
        
        void push(int value) {
            if (size == capacity) {
                // Reallocation might throw, but we handle it
                size_t new_capacity = capacity == 0 ? 1 : capacity * 2;
                int* new_data = new(nothrow) int[new_capacity];
                if (!new_data) {
                    throw bad_alloc();
                }
                
                // Copy existing elements (might throw)
                for (size_t i = 0; i < size; ++i) {
                    new_data[i] = data[i];
                }
                
                delete[] data;
                data = new_data;
                capacity = new_capacity;
            }
            
            data[size++] = value;  // Might throw for complex types
        }
        
        // Basic guarantee: if push fails, stack remains valid
        // (might have reallocated but original data preserved or restored)
    };
    
    cout << "\nDesigning for exception safety:" << endl;
    cout << "1. Use RAII for all resources" << endl;
    cout << "2. Perform operations that might fail on copies/temporaries" << endl;
    cout << "3. Commit changes only after all operations succeed" << endl;
    cout << "4. Provide at least basic guarantee for all operations" << endl;
    
    cout << endl;
}

void demonstrateCopyAndSwap() {
    cout << "=== Copy-and-Swap Idiom for Strong Exception Safety ===" << endl;
    
    // Copy-and-swap provides strong exception safety
    class String {
    private:
        char* data;
        size_t length;
        
        void free() {
            delete[] data;
            data = nullptr;
            length = 0;
        }
        
        void copyFrom(const String& other) {
            length = other.length;
            data = new char[length + 1];
            for (size_t i = 0; i <= length; ++i) {
                data[i] = other.data[i];
            }
        }
        
    public:
        // Constructor
        String(const char* str = "") {
            length = 0;
            while (str[length] != '\0') ++length;
            
            data = new char[length + 1];
            for (size_t i = 0; i <= length; ++i) {
                data[i] = str[i];
            }
        }
        
        // Destructor
        ~String() {
            free();
        }
        
        // Copy constructor
        String(const String& other) {
            copyFrom(other);
        }
        
        // Copy assignment with copy-and-swap (strong exception safety)
        String& operator=(String other) {  // Pass by value (copy)
            swap(*this, other);  // Non-throwing swap
            return *this;
        }
        
        // Move constructor
        String(String&& other) noexcept 
            : data(other.data), length(other.length) {
            other.data = nullptr;
            other.length = 0;
        }
        
        // Move assignment
        String& operator=(String&& other) noexcept {
            if (this != &other) {
                free();
                data = other.data;
                length = other.length;
                other.data = nullptr;
                other.length = 0;
            }
            return *this;
        }
        
        // Swap function (noexcept)
        friend void swap(String& a, String& b) noexcept {
            using std::swap;
            swap(a.data, b.data);
            swap(a.length, b.length);
        }
        
        const char* c_str() const { return data; }
    };
    
    // Test copy-and-swap
    String s1 = "Hello";
    String s2 = "World";
    
    cout << "Before swap:" << endl;
    cout << "s1: " << s1.c_str() << endl;
    cout << "s2: " << s2.c_str() << endl;
    
    swap(s1, s2);
    
    cout << "\nAfter swap:" << endl;
    cout << "s1: " << s1.c_str() << endl;
    cout << "s2: " << s2.c_str() << endl;
    
    // Copy assignment provides strong guarantee
    s1 = s2;  // If allocation fails in copy, s1 remains unchanged
    
    cout << "\nBenefits of copy-and-swap:" << endl;
    cout << "1. Provides strong exception safety" << endl;
    cout << "2. Eliminates code duplication" << endl;
    cout << "3. Handles self-assignment correctly" << endl;
    cout << "4. Works with move semantics" << endl;
    
    cout << endl;
}

void demonstrateResourceManagementPatterns() {
    cout << "=== Resource Management Patterns ===" << endl;
    
    // 1. Scoped Lock pattern
    class ScopedLock {
    private:
        mutex& mtx;
    public:
        explicit ScopedLock(mutex& m) : mtx(m) {
            mtx.lock();
            cout << "Mutex locked" << endl;
        }
        
        ~ScopedLock() {
            mtx.unlock();
            cout << "Mutex unlocked" << endl;
        }
        
        // Delete copy operations
        ScopedLock(const ScopedLock&) = delete;
        ScopedLock& operator=(const ScopedLock&) = delete;
    };
    
    mutex mtx;
    
    {
        ScopedLock lock(mtx);  // Automatically locks
        // Critical section
        // Mutex automatically unlocked when lock goes out of scope
    }
    
    // 2. Transaction pattern
    class DatabaseTransaction {
    private:
        bool committed;
        
        void beginTransaction() {
            cout << "Transaction began" << endl;
        }
        
        void rollbackTransaction() {
            cout << "Transaction rolled back" << endl;
        }
        
        void commitTransaction() {
            cout << "Transaction committed" << endl;
        }
        
    public:
        DatabaseTransaction() : committed(false) {
            beginTransaction();
        }
        
        ~DatabaseTransaction() {
            if (!committed) {
                rollbackTransaction();
            }
        }
        
        void commit() {
            commitTransaction();
            committed = true;
        }
        
        // Execute operation with rollback on failure
        template<typename Func>
        void execute(Func operation) {
            try {
                operation();
            }
            catch (...) {
                rollbackTransaction();
                throw;
            }
        }
    };
    
    // 3. Scope Guard (C++11 and later)
    class ScopeGuard {
    private:
        function<void()> cleanup;
    public:
        explicit ScopeGuard(function<void()> f) : cleanup(f) {}
        
        ~ScopeGuard() {
            if (cleanup) {
                cleanup();
            }
        }
        
        // Release the cleanup (for commit scenarios)
        void release() {
            cleanup = nullptr;
        }
        
        // Delete copy operations
        ScopeGuard(const ScopeGuard&) = delete;
        ScopeGuard& operator=(const ScopeGuard&) = delete;
    };
    
    // Using scope guard
    cout << "\nScope Guard example:" << endl;
    {
        int* resource = new int(42);
        ScopeGuard guard([&resource]() {
            delete resource;
            cout << "Resource cleaned up by scope guard" << endl;
        });
        
        // Use resource
        // If exception occurs here, guard cleans up
        
        guard.release();  // Explicit cleanup if successful
        delete resource;
        cout << "Resource cleaned up explicitly" << endl;
    }
    
    cout << "\nRAII Best Practices:" << endl;
    cout << "1. Acquire resources in constructors" << endl;
    cout << "2. Release resources in destructors" << endl;
    cout << "3. Use smart pointers for dynamic memory" << endl;
    cout << "4. Make swap operations noexcept" << endl;
    cout << "5. Provide at least basic exception safety" << endl;
    cout << "6. Document exception safety guarantees" << endl;
    
    cout << endl;
}

void demonstrateSmartPointersAndRAII() {
    cout << "=== Smart Pointers as RAII Wrappers ===" << endl;
    
    // 1. unique_ptr - exclusive ownership
    cout << "1. unique_ptr (exclusive ownership):" << endl;
    {
        unique_ptr<int> ptr(new int(42));
        cout << "Value: " << *ptr << endl;
        // Automatically deleted when out of scope
    }
    
    // 2. shared_ptr - shared ownership
    cout << "\n2. shared_ptr (shared ownership):" << endl;
    {
        shared_ptr<int> ptr1 = make_shared<int>(100);
        shared_ptr<int> ptr2 = ptr1;  // Both share ownership
        
        cout << "Use count: " << ptr1.use_count() << endl;
        cout << "Value via ptr1: " << *ptr1 << endl;
        cout << "Value via ptr2: " << *ptr2 << endl;
    }
    
    // 3. Custom deleter with unique_ptr
    cout << "\n3. Custom deleter:" << endl;
    {
        FILE* file = fopen("test.txt", "w");
        if (file) {
            unique_ptr<FILE, decltype(&fclose)> filePtr(file, fclose);
            // File automatically closed when filePtr goes out of scope
            cout << "File will be automatically closed" << endl;
        }
    }
    
    // 4. RAII with containers
    cout << "\n4. RAII with containers:" << endl;
    {
        vector<unique_ptr<int>> numbers;
        numbers.push_back(make_unique<int>(1));
        numbers.push_back(make_unique<int>(2));
        numbers.push_back(make_unique<int>(3));
        
        // All memory automatically managed
        for (const auto& ptr : numbers) {
            cout << *ptr << " ";
        }
        cout << endl;
    }
    
    cout << "\nBenefits of smart pointers:" << endl;
    cout << "- Automatic memory management" << endl;
    cout << "- Exception safety" << endl;
    cout << "- Clear ownership semantics" << endl;
    cout << "- No manual new/delete" << endl;
    cout << "- Custom deleters for non-memory resources" << endl;
    
    cout << endl;
}

int main() {
    demonstrateBasicRAII();
    demonstrateExceptionSafetyGuarantees();
    demonstrateCopyAndSwap();
    demonstrateResourceManagementPatterns();
    demonstrateSmartPointersAndRAII();
    
    return 0;
}
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.

Advanced Exception Handling Examples
#include <iostream>
#include <stdexcept>
#include <memory>
#include <vector>
#include <fstream>
#include <system_error>
#include <future>
#include <thread>
using namespace std;

void demonstrateNestedExceptions() {
    cout << "=== Nested Exceptions (C++11) ===" << endl;
    
    // Nested exceptions allow preserving exception context
    
    // 1. Basic nested exception usage
    void lowLevelFunction() {
        throw runtime_error("Low-level error occurred");
    }
    
    void midLevelFunction() {
        try {
            lowLevelFunction();
        }
        catch (...) {
            // Wrap current exception in nested_exception
            throw_with_nested(
                runtime_error("Mid-level function failed")
            );
        }
    }
    
    void highLevelFunction() {
        try {
            midLevelFunction();
        }
        catch (...) {
            throw_with_nested(
                logic_error("High-level operation failed")
            );
        }
    }
    
    // 2. Catching and unwrapping nested exceptions
    try {
        highLevelFunction();
    }
    catch (const exception& e) {
        cout << "Caught exception: " << e.what() << endl;
        
        // Try to rethrow nested exception
        try {
            rethrow_if_nested(e);
        }
        catch (const exception& nested) {
            cout << "  Nested: " << nested.what() << endl;
            
            // Could continue unwrapping...
            try {
                rethrow_if_nested(nested);
            }
            catch (const exception& inner) {
                cout << "    Inner: " << inner.what() << endl;
            }
        }
    }
    
    // 3. Custom nested exception class
    class DatabaseException : public nested_exception {
    private:
        string operation;
    public:
        DatabaseException(const string& op) : operation(op) {}
        
        const char* what() const noexcept override {
            return ("Database operation failed: " + operation).c_str();
        }
    };
    
    void databaseOperation() {
        try {
            throw runtime_error("Connection timeout");
        }
        catch (...) {
            throw_with_nested(DatabaseException("SELECT * FROM users"));
        }
    }
    
    try {
        databaseOperation();
    }
    catch (const DatabaseException& e) {
        cout << "\nDatabase error: " << e.what() << endl;
        
        // Print nested exception chain
        auto print_exception_chain = [](const exception& e, int level = 0) {
            cout << string(level * 2, ' ') << e.what() << endl;
            try {
                rethrow_if_nested(e);
            }
            catch (const exception& nested) {
                print_exception_chain(nested, level + 1);
            }
        };
        
        print_exception_chain(e);
    }
    
    cout << "\nBenefits of nested exceptions:" << endl;
    cout << "1. Preserve full error context" << endl;
    cout << "2. Debugging with complete stack trace" << endl;
    cout << "3. Error reporting with full details" << endl;
    cout << "4. Useful in layered architectures" << endl;
    
    cout << endl;
}

void demonstrateExceptionPtr() {
    cout << "=== std::exception_ptr (C++11) ===" << endl;
    
    // exception_ptr can store exceptions for later use
    
    // 1. Basic exception_ptr usage
    exception_ptr saved_exception;
    
    void mightThrow() {
        throw runtime_error("An error occurred");
    }
    
    void saveException() {
        try {
            mightThrow();
        }
        catch (...) {
            saved_exception = current_exception();
        }
    }
    
    saveException();
    
    // Re-throw saved exception later
    if (saved_exception) {
        cout << "Re-throwing saved exception:" << endl;
        try {
            rethrow_exception(saved_exception);
        }
        catch (const exception& e) {
            cout << "Caught: " << e.what() << endl;
        }
    }
    
    // 2. Exception_ptr with asynchronous operations
    vector<exception_ptr> exceptions;
    
    auto task1 = []() -> int {
        throw runtime_error("Task 1 failed");
        return 1;
    };
    
    auto task2 = []() -> int {
        return 42;  // Success
    };
    
    auto task3 = []() -> int {
        throw logic_error("Task 3 logic error");
        return 3;
    };
    
    // Simulate async execution
    vector<function<int()>> tasks = {task1, task2, task3};
    
    for (auto& task : tasks) {
        try {
            int result = task();
            cout << "Task succeeded: " << result << endl;
        }
        catch (...) {
            exceptions.push_back(current_exception());
        }
    }
    
    // Handle all exceptions later
    cout << "\nProcessing " << exceptions.size() << " saved exceptions:" << endl;
    for (const auto& ex_ptr : exceptions) {
        try {
            rethrow_exception(ex_ptr);
        }
        catch (const runtime_error& e) {
            cout << "Runtime error: " << e.what() << endl;
        }
        catch (const logic_error& e) {
            cout << "Logic error: " << e.what() << endl;
        }
        catch (const exception& e) {
            cout << "Generic error: " << e.what() << endl;
        }
    }
    
    // 3. Exception_ptr in multithreading
    cout << "\nException handling in threads:" << endl;
    
    exception_ptr thread_exception;
    
    auto thread_func = [&thread_exception]() {
        try {
            // Simulate work that might fail
            this_thread::sleep_for(chrono::milliseconds(100));
            throw runtime_error("Thread execution failed");
        }
        catch (...) {
            thread_exception = current_exception();
        }
    };
    
    thread t(thread_func);
    t.join();
    
    if (thread_exception) {
        try {
            rethrow_exception(thread_exception);
        }
        catch (const exception& e) {
            cout << "Thread threw: " << e.what() << endl;
        }
    }
    
    cout << "\nUse cases for exception_ptr:" << endl;
    cout << "1. Asynchronous exception handling" << endl;
    cout << "2. Exception propagation across threads" << endl;
    cout << "3. Deferred exception handling" << endl;
    cout << "4. Error aggregation and batch processing" << endl;
    
    cout << endl;
}

void demonstrateSystemError() {
    cout << "=== std::system_error (C++11) ===" << endl;
    
    // system_error for operating system errors
    
    // 1. Basic system_error usage
    try {
        ifstream file("nonexistent.txt");
        file.exceptions(ios::failbit);
        
        string line;
        getline(file, line);
    }
    catch (const system_error& e) {
        cout << "System error: " << e.what() << endl;
        cout << "Error code: " << e.code() << endl;
        cout << "Error value: " << e.code().value() << endl;
        cout << "Error category: " << e.code().category().name() << endl;
        cout << "Error message: " << e.code().message() << endl;
    }
    
    // 2. Creating custom system_error
    enum class NetworkError {
        connection_failed = 1,
        timeout = 2,
        protocol_error = 3
    };
    
    class NetworkErrorCategory : public error_category {
    public:
        const char* name() const noexcept override {
            return "network";
        }
        
        string message(int ev) const override {
            switch (static_cast<NetworkError>(ev)) {
                case NetworkError::connection_failed:
                    return "Connection failed";
                case NetworkError::timeout:
                    return "Connection timeout";
                case NetworkError::protocol_error:
                    return "Protocol error";
                default:
                    return "Unknown network error";
            }
        }
    };
    
    const NetworkErrorCategory network_error_category{};
    
    error_code make_error_code(NetworkError e) {
        return {static_cast<int>(e), network_error_category};
    }
    
    // 3. Using custom error codes
    void connectToServer() {
        // Simulate network error
        throw system_error(make_error_code(NetworkError::timeout),
                          "Failed to connect to server");
    }
    
    try {
        connectToServer();
    }
    catch (const system_error& e) {
        cout << "\nNetwork error: " << e.what() << endl;
        if (e.code().category() == network_error_category) {
            cout << "Network error code: " << e.code().value() << endl;
            cout << "Network error message: " << e.code().message() << endl;
        }
    }
    
    // 4. Error conditions
    cout << "\nError conditions:" << endl;
    error_code ec = make_error_code(NetworkError::timeout);
    error_condition cond = ec.default_error_condition();
    
    cout << "Error code: " << ec.value() << endl;
    cout << "Error condition: " << cond.value() << endl;
    cout << "Condition category: " << cond.category().name() << endl;
    
    cout << "\nBenefits of system_error:" << endl;
    cout << "1. Standard way to handle system/OS errors" << endl;
    cout << "2. Extensible with custom error categories" << endl;
    cout << "3. Portable error code handling" << endl;
    cout << "4. Integration with std::error_code and std::error_condition" << endl;
    
    cout << endl;
}

void demonstrateFutureExceptions() {
    cout << "=== Exception Handling with std::future (C++11) ===" << endl;
    
    // Futures can propagate exceptions from async operations
    
    // 1. Basic future exception handling
    auto computeValue = []() -> int {
        this_thread::sleep_for(chrono::milliseconds(50));
        throw runtime_error("Computation failed");
        return 42;
    };
    
    future<int> fut = async(launch::async, computeValue);
    
    try {
        int result = fut.get();  // Will throw exception from computeValue
        cout << "Result: " << result << endl;
    }
    catch (const exception& e) {
        cout << "Exception from future: " << e.what() << endl;
    }
    
    // 2. Multiple futures with exception handling
    cout << "\nMultiple futures:" << endl;
    vector<future<int>> futures;
    
    for (int i = 0; i < 5; ++i) {
        futures.push_back(async(launch::async, [i]() -> int {
            if (i == 2) {
                throw runtime_error("Task " + to_string(i) + " failed");
            }
            return i * 10;
        }));
    }
    
    for (auto& f : futures) {
        try {
            int result = f.get();
            cout << "Task succeeded: " << result << endl;
        }
        catch (const exception& e) {
            cout << "Task failed: " << e.what() << endl;
        }
    }
    
    // 3. promise for explicit exception setting
    cout << "\nUsing promise to set exceptions:" << endl;
    
    promise<int> prom;
    future<int> fut2 = prom.get_future();
    
    // Thread that might fail
    thread worker([&prom]() {
        try {
            // Simulate work
            this_thread::sleep_for(chrono::milliseconds(100));
            throw logic_error("Worker thread failed");
            
            // If success:
            // prom.set_value(42);
        }
        catch (...) {
            // Set exception on promise
            prom.set_exception(current_exception());
        }
    });
    
    try {
        int result = fut2.get();
        cout << "Worker result: " << result << endl;
    }
    catch (const exception& e) {
        cout << "Worker failed: " << e.what() << endl;
    }
    
    worker.join();
    
    // 4. shared_future for multiple readers
    cout << "\nshared_future for multiple exception handlers:" << endl;
    
    promise<int> prom2;
    shared_future<int> shared_fut = prom2.get_future().share();
    
    // Multiple threads can wait on the same shared_future
    vector<thread> readers;
    for (int i = 0; i < 3; ++i) {
        readers.emplace_back([shared_fut, i]() {
            try {
                int result = shared_fut.get();
                cout << "Reader " << i << " got: " << result << endl;
            }
            catch (const exception& e) {
                cout << "Reader " << i << " caught: " << e.what() << endl;
            }
        });
    }
    
    // Set exception (or value) on promise
    prom2.set_exception(make_exception_ptr(runtime_error("Shared computation failed")));
    
    for (auto& t : readers) {
        t.join();
    }
    
    cout << "\nFuture exception handling benefits:" << endl;
    cout << "1. Exception propagation across threads" << endl;
    cout << "2. Asynchronous error handling" << endl;
    cout << "3. Clean separation of error production and consumption" << endl;
    cout << "4. Support for multiple exception consumers" << endl;
    
    cout << endl;
}

void demonstrateExceptionHandlingStrategies() {
    cout << "=== Exception Handling Strategies ===" << endl;
    
    // Different strategies for different scenarios
    
    cout << "1. Let it throw (propagate to caller):" << endl;
    cout << "   - When caller can handle error meaningfully" << endl;
    cout << "   - In library code without UI" << endl;
    cout << "   - When error recovery is possible at higher level" << endl;
    
    cout << "\n2. Catch and translate:" << endl;
    cout << "   - Convert low-level exceptions to high-level" << endl;
    cout << "   - Add context information" << endl;
    cout << "   - Use nested exceptions for stack traces" << endl;
    
    cout << "\n3. Catch and recover:" << endl;
    cout << "   - When alternative path exists" << endl;
    cout << "   - For non-critical failures" << endl;
    cout << "   - With logging of the incident" << endl;
    
    cout << "\n4. Catch and rethrow:" << endl;
    cout << "   - Cleanup resources before rethrowing" << endl;
    cout << "   - Add logging or telemetry" << endl;
    cout << "   - Transform exception type if needed" << endl;
    
    cout << "\n5. Catch and terminate:" << endl;
    cout << "   - For unrecoverable errors" << endl;
    cout << "   - When program state is corrupted" << endl;
    cout << "   - In destructors (must not throw)" << endl;
    
    // Example: Strategy pattern for exception handling
    class ExceptionHandler {
    public:
        virtual ~ExceptionHandler() = default;
        virtual void handle(exception_ptr eptr) = 0;
    };
    
    class LogAndRethrowHandler : public ExceptionHandler {
    public:
        void handle(exception_ptr eptr) override {
            try {
                rethrow_exception(eptr);
            }
            catch (const exception& e) {
                cerr << "ERROR: " << e.what() << endl;
                // Log to file, send to monitoring, etc.
                throw;
            }
        }
    };
    
    class TranslateAndThrowHandler : public ExceptionHandler {
    public:
        void handle(exception_ptr eptr) override {
            try {
                rethrow_exception(eptr);
            }
            catch (const system_error& e) {
                // Translate system errors to application errors
                throw runtime_error("System operation failed: " + string(e.what()));
            }
            catch (const exception& e) {
                // Wrap other exceptions
                throw_with_nested(
                    runtime_error("Operation failed: " + string(e.what()))
                );
            }
        }
    };
    
    cout << "\nChoosing a strategy depends on:" << endl;
    cout << "- Application type (CLI, GUI, server, library)" << endl;
    cout << "- Error criticality" << endl;
    cout << "- User experience requirements" << endl;
    cout << "- Logging and monitoring infrastructure" << endl;
    cout << "- Team conventions and coding standards" << endl;
    
    cout << endl;
}

void demonstrateErrorHandlingAlternatives() {
    cout << "=== Alternatives to Exceptions ===" << endl;
    
    // Sometimes exceptions aren't the right choice
    
    cout << "1. Return error codes:" << endl;
    cout << "   - For performance-critical code" << endl;
    cout << "   - When exceptions are disabled" << endl;
    cout << "   - In C interfaces or callbacks" << endl;
    
    enum class ErrorCode {
        Success = 0,
        FileNotFound,
        PermissionDenied,
        InvalidFormat,
        OutOfMemory
    };
    
    ErrorCode readFile(const string& filename, string& content) {
        ifstream file(filename);
        if (!file) return ErrorCode::FileNotFound;
        
        // ... read file
        
        return ErrorCode::Success;
    }
    
    cout << "\n2. std::optional (C++17):" << endl;
    cout << "   - For operations that might not produce a result" << endl;
    cout << "   - When 'no value' is a valid outcome" << endl;
    
    optional<int> safeDivide(int a, int b) {
        if (b == 0) return nullopt;
        return a / b;
    }
    
    cout << "\n3. std::expected (C++23):" << endl;
    cout << "   - Combines value and error information" << endl;
    cout << "   - Like std::optional but with error details" << endl;
    
    cout << "\n4. Assertions:" << endl;
    cout << "   - For programming errors (bugs)" << endl;
    cout << "   - During development and testing" << endl;
    cout << "   - Often disabled in release builds" << endl;
    
    #define CHECK(condition, message) \
        if (!(condition)) { \
            cerr << "Assertion failed: " << #condition << " - " << message << endl; \
            abort(); \
        }
    
    cout << "\n5. When NOT to use exceptions:" << endl;
    cout << "- In destructors" << endl;
    cout << "- Across module boundaries (e.g., DLLs)" << endl;
    cout << "- In performance-critical inner loops" << endl;
    cout << "- When exception support is disabled" << endl;
    cout << "- For control flow (exceptions are for exceptional conditions)" << endl;
    
    cout << "\n6. Mixed approach:" << endl;
    cout << "- Use exceptions for unexpected errors" << endl;
    cout << "- Use error codes for expected failures" << endl;
    cout << "- Use assertions for invariants and preconditions" << endl;
    cout << "- Document which approach each function uses" << endl;
    
    cout << endl;
}

int main() {
    demonstrateNestedExceptions();
    demonstrateExceptionPtr();
    demonstrateSystemError();
    demonstrateFutureExceptions();
    demonstrateExceptionHandlingStrategies();
    demonstrateErrorHandlingAlternatives();
    
    return 0;
}
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
Good vs Bad Exception Handling
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>
using namespace std;

// BAD PRACTICE examples
void demonstrateBadPractices() {
    cout << "=== BAD PRACTICES ===" << endl;
    
    // 1. Empty catch block (swallowing exceptions)
    try {
        throw runtime_error("Important error!");
    }
    catch (...) {
        // BAD: Swallowing exception - no one knows it happened!
    }
    
    // 2. Catching by value (object slicing)
    try {
        throw runtime_error("Error message");
    }
    catch (exception e) {  // BAD: Slices the exception object
        cout << "Caught: " << e.what() << endl;
    }
    
    // 3. Throwing from destructor
    class BadDestructor {
    public:
        ~BadDestructor() {
            throw runtime_error("Exception in destructor");  // VERY BAD!
        }
    };
    
    // 4. Manual resource management without RAII
    void leakyFunction() {
        int* resource = new int(42);
        
        if (/* some condition */) {
            throw runtime_error("Error");
            // BAD: Memory leak - delete never called
        }
        
        delete resource;
    }
    
    // 5. Using exceptions for normal control flow
    void findElement(const vector<int>& v, int value) {
        try {
            for (size_t i = 0; i < v.size(); ++i) {
                if (v[i] == value) {
                    throw i;  // BAD: Using exception for normal flow
                }
            }
            throw -1;
        }
        catch (size_t index) {
            cout << "Found at index " << index << endl;
        }
        catch (int) {
            cout << "Not found" << endl;
        }
    }
    
    // 6. Not providing exception safety
    class UnsafeVector {
    private:
        int* data;
        size_t size;
        size_t capacity;
    public:
        void push_back(int value) {
            if (size == capacity) {
                // Reallocate without preserving old data on exception
                int* new_data = new int[capacity * 2];  // Might throw
                // If exception here, old data is lost!
                delete[] data;
                data = new_data;
                capacity *= 2;
            }
            data[size++] = value;
        }
        // BAD: No exception safety guarantee
    };
    
    cout << endl;
}

// GOOD PRACTICE examples
void demonstrateGoodPractices() {
    cout << "=== GOOD PRACTICES ===" << endl;
    
    // 1. Proper catch block
    try {
        throw runtime_error("Important error!");
    }
    catch (const exception& e) {  // GOOD: Catch by const reference
        cerr << "Error logged: " << e.what() << endl;
        // Handle or rethrow appropriately
    }
    
    // 2. RAII for resource management
    void safeFunction() {
        unique_ptr<int> resource = make_unique<int>(42);
        
        if (/* some condition */) {
            throw runtime_error("Error");
            // GOOD: resource automatically deleted by unique_ptr destructor
        }
        
        // Use resource...
    }
    
    // 3. Proper destructor
    class GoodDestructor {
    public:
        ~GoodDestructor() noexcept {  // GOOD: noexcept destructor
            // Cleanup that won't throw
        }
    };
    
    // 4. Exception safety guarantees
    class SafeVector {
    private:
        unique_ptr<int[]> data;
        size_t size;
        size_t capacity;
        
        void reallocate(size_t new_capacity) {
            unique_ptr<int[]> new_data(new int[new_capacity]);
            
            // Copy old data
            for (size_t i = 0; i < size; ++i) {
                new_data[i] = data[i];
            }
            
            // Commit changes (non-throwing operations only)
            data = move(new_data);
            capacity = new_capacity;
        }
        
    public:
        // Strong exception safety guarantee
        void push_back(int value) {
            if (size == capacity) {
                reallocate(capacity == 0 ? 1 : capacity * 2);
            }
            data[size++] = value;
        }
    };
    
    // 5. Using proper control flow
    optional<size_t> findElementGood(const vector<int>& v, int value) {
        for (size_t i = 0; i < v.size(); ++i) {
            if (v[i] == value) {
                return i;  // GOOD: Return value, not exception
            }
        }
        return nullopt;  // GOOD: Clear "not found" indication
    }
    
    // 6. noexcept where appropriate
    class EfficientClass {
    public:
        EfficientClass() noexcept = default;
        ~EfficientClass() noexcept = default;
        
        // Move operations should be noexcept
        EfficientClass(EfficientClass&&) noexcept = default;
        EfficientClass& operator=(EfficientClass&&) noexcept = default;
        
        // Copy operations
        EfficientClass(const EfficientClass&) = default;
        EfficientClass& operator=(const EfficientClass&) = default;
    };
    
    // 7. Proper exception hierarchy
    class FileSystemException : public runtime_error {
    public:
        FileSystemException(const string& msg, const string& path)
            : runtime_error(msg + ": " + path), filepath(path) {}
        
        const string& getFilePath() const { return filepath; }
        
    private:
        string filepath;
    };
    
    class FileNotFoundException : public FileSystemException {
    public:
        FileNotFoundException(const string& path)
            : FileSystemException("File not found", path) {}
    };
    
    class PermissionDeniedException : public FileSystemException {
    public:
        PermissionDeniedException(const string& path)
            : FileSystemException("Permission denied", path) {}
    };
    
    // 8. Cleanup with RAII even in catch blocks
    void processWithCleanup() {
        auto resource = make_unique<int>(42);
        
        try {
            // Operations that might throw
            throw runtime_error("Operation failed");
        }
        catch (...) {
            // Resource still automatically cleaned up
            // Additional cleanup if needed
            throw;  // Rethrow after cleanup
        }
    }
    
    // 9. Documenting exception guarantees
    /**
     * @brief Performs calculation
     * @param input Input value
     * @return Calculation result
     * @throws invalid_argument if input is negative
     * @throws overflow_error if result exceeds maximum value
     * 
     * Exception safety: Strong guarantee
     */
    int calculate(int input) {
        if (input < 0) {
            throw invalid_argument("Input must be non-negative");
        }
        
        // Calculation...
        return input * 2;
    }
    
    // 10. Using standard exceptions when appropriate
    void validateAge(int age) {
        if (age < 0) {
            throw invalid_argument("Age cannot be negative");  // GOOD: Standard exception
        }
        if (age > 150) {
            throw out_of_range("Age is not realistic");  // GOOD: Standard exception
        }
    }
    
    cout << "\nSummary of good practices:" << endl;
    cout << "1. Use RAII for all resources" << endl;
    cout << "2. Throw by value, catch by const reference" << endl;
    cout << "3. Never throw from destructors" << endl;
    cout << "4. Mark destructors as noexcept" << endl;
    cout << "5. Document exception guarantees" << endl;
    cout << "6. Provide at least basic exception safety" << endl;
    cout << "7. Use standard exceptions when they fit" << endl;
    cout << "8. Catch at appropriate level of abstraction" << endl;
    cout << "9. Don't use exceptions for normal control flow" << endl;
    cout << "10. Log exceptions before handling (when appropriate)" << endl;
    
    cout << endl;
}

void demonstratePerformanceConsiderations() {
    cout << "=== Performance Considerations ===" << endl;
    
    cout << "Exception handling has overhead:" << endl;
    cout << "1. No overhead when no exceptions are thrown" << endl;
    cout << "2. Stack unwinding is expensive when exceptions occur" << endl;
    cout << "3. Code size increases due to exception tables" << endl;
    
    cout << "\nWhen exceptions affect performance:" << endl;
    cout << "- In tight loops with frequent throwing" << endl;
    cout << "- Real-time systems with strict timing requirements" << endl;
    cout << "- Performance-critical library code" << endl;
    
    cout << "\nOptimization tips:" << endl;
    cout << "1. Use noexcept for functions that won't throw" << endl;
    cout << "2. Avoid exceptions in performance-critical paths" << endl;
    cout << "3. Consider error codes for high-frequency operations" << endl;
    cout << "4. Profile to identify actual performance issues" << endl;
    
    cout << "\nRemember: Exceptions are for EXCEPTIONAL conditions" << endl;
    cout << "They shouldn't be part of normal program flow" << endl;
    
    cout << endl;
}

int main() {
    demonstrateBadPractices();
    demonstrateGoodPractices();
    demonstratePerformanceConsiderations();
    
    return 0;
}

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