Skip to main content

Advanced C++

Exception Handling

Complete lesson for Exception Handling using LEARNCODEGURU’s 6-level learning model — from basics to advanced practice, with C++ OOP and STL where relevant.

L1

Basics — Exception Handling

Section: Advanced C++. C++ exception handling — try, catch, throw, standard exceptions, noexcept, and robust error design.

What is an exception?

  • An **exception** is an event during execution that **disrupts normal flow** — typically a **runtime error**.
  • Exception handling lets you **separate** error-handling code from normal logic.
  • Handled well, the program can recover or exit cleanly instead of crashing abruptly.
  • **Examples:** division by zero, file not found, invalid input, memory allocation failure.
  • **Real-life:** ATM cannot dispense cash — show a clear message instead of freezing the machine.
IdeaMeaning
ExceptionRuntime problem that breaks normal flow
throwSignal the problem by throwing an object
tryGuard code that might throw
catchHandle a matching thrown type
GoalKeep the program robust and readable

Exception handling — catch errors, keep flowing

Exceptions handle runtime errors without abrupt crashes. The diagram covers try/catch/throw, multiple and nested handlers, standard and custom exceptions, catch order, noexcept, benefits, and use cases.

C++ exception handling infographic: what is an exception, why use it, keywords try catch throw noexcept, how it works flowchart, basic multiple nested standard and custom examples, exception specifications, catch order, important points, benefits and use cases.
C++ exception handling — expect the unexpected, handle it gracefully
  • try: code that might fail.
  • throw: signal the error with an object.
  • catch: handle matching types (derived before base).
  • Cleanup: no finally — use RAII / destructors.

Why use exception handling?

  • Prevents abnormal program termination when errors are handled.
  • Improves robustness and reliability.
  • Separates error handling from happy-path code.
  • Makes code cleaner and easier to maintain.
  • Lets errors **propagate** up to the layer best able to handle them.
BenefitWhat you gain
No abrupt crashCatch and respond instead of terminate
Cleaner logicBusiness code not littered with error ifs
PropagationCaller can decide how to recover
Better reportingMessages via what() / custom types
Works with RAIIDestructors still run during unwinding

Keywords in C++

  • Core trio: **try**, **catch**, **throw**.
  • C++11+: prefer **noexcept** to say a function will not throw.
  • Old throw() dynamic exception specs are obsolete — avoid them.
  • There is **no finally** in C++ — use **destructors / RAII** for cleanup.
KeywordDescription
tryEncloses code that may throw
catchCatches and handles a matching exception
throwThrows an exception object
noexceptFunction is guaranteed not to throw (C++11+)
throw() (old)Obsolete “does not throw” spec — prefer noexcept
finallyNot in C++ — use RAII / destructors

How exception handling works

  • Code inside try runs normally.
  • If an exception is thrown, control **jumps** to the matching catch.
  • If handled, execution continues after the try/catch.
  • If **no** matching catch exists, std::terminate() is called (program aborts).
StepWhat happens
1Execute statements in try
2On throw → find matching catch
3Run handler; then continue after try/catch
4No match → terminate

Basic try / catch / throw

  • Throw any copyable/movable object (often a string, int, or exception class).
  • Catch by **const reference** for class types: catch (const T& e).
  • After a handled exception, the program can continue.
#include <iostream>
using namespace std;

int main() {
    try {
        int a = 10, b = 0;
        if (b == 0)
            throw "Division by zero!";
        cout << a / b;
    } catch (const char* msg) {
        cout << "Exception: " << msg << "\n";
    }
    cout << "Program continues normally.\n";
    return 0;
}

Multiple catch blocks

  • One try can have several catch clauses for different types.
  • catch (...) is a **catch-all** — place it **last**.
  • Catch blocks are tried **in order**.
#include <iostream>
using namespace std;

int main() {
    try {
        int arr[5] = {};
        int index;
        cout << "Enter index: ";
        cin >> index;
        if (index < 0 || index >= 5)
            throw index;
        cout << arr[index] << "\n";
    } catch (int e) {
        cout << "Invalid index: " << e << "\n";
    } catch (...) {
        cout << "Some unknown exception!\n";
    }
    return 0;
}

Nested try-catch and rethrow

  • try/catch can nest; an inner handler may **rethrow** with bare throw;.
  • Rethrow keeps the original exception object for an outer handler.
#include <iostream>
using namespace std;

int main() {
    try {
        try {
            throw 100;
        } catch (int e) {
            cout << "Inner catch: " << e << "\n";
            throw; // rethrow
        }
    } catch (int e) {
        cout << "Outer catch: " << e << "\n";
    }
    return 0;
}

Standard exceptions

  • Prefer types from / that inherit std::exception.
  • Common: runtime_error, logic_error, invalid_argument, out_of_range, overflow_error.
  • Use e.what() for a message string.
  • Catch the base as catch (const exception& e) when you want one handler.
#include <iostream>
#include <stdexcept>
using namespace std;

int main() {
    try {
        int n;
        cout << "Enter a positive number: ";
        cin >> n;
        if (n <= 0)
            throw invalid_argument("Number must be positive");
        cout << "Number is: " << n << "\n";
    } catch (const invalid_argument& e) {
        cout << "Exception: " << e.what() << "\n";
    }
    return 0;
}

Custom exception class

  • You can throw your own type; better: derive from std::exception or runtime_error.
  • Always catch custom types by reference.
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

class MyException : public runtime_error {
public:
    explicit MyException(const string& m) : runtime_error(m) {}
};

int main() {
    try {
        throw MyException("This is a custom exception!");
    } catch (const MyException& e) {
        cout << "Caught: " << e.what() << "\n";
    }
    return 0;
}

Order of catch blocks

  • Handlers are checked **top to bottom**.
  • Catch **derived** types **before** base types.
  • Wrong order: a base catch can swallow everything so derived handlers never run.
OrderExample
Correctcatch (Derived&) → catch (Base&) → catch (...)
Wrongcatch (Base&) first — Derived never matched separately
try {
    /* ... */
} catch (const invalid_argument& e) { /* specific */ }
  catch (const logic_error& e) { /* broader */ }
  catch (const exception& e) { /* base */ }
  catch (...) { /* last resort */ }

Key points to remember

  • Exception handling uses try, catch, and throw.
  • No matching catch → std::terminate().
  • Use exceptions for **exceptional** situations — not normal control flow.
  • Rethrow with bare throw;.
  • Destructors run during **stack unwinding** — RAII keeps cleanup safe.
  • Prefer noexcept over obsolete throw().
  • Common use cases: files, memory, network, invalid input, database errors.
L2

Advanced — Exception Handling

Stack unwinding and RAII

  • When an exception leaves a scope, local objects are destroyed in reverse order of construction.
  • RAII types (fstream, unique_ptr, lock_guard) release resources automatically.
  • Never throw from a destructor during unwinding — that can call terminate.

noexcept

  • void f() noexcept; means f promises not to throw.
  • If it does throw anyway, std::terminate() is called.
  • Move constructors/assignments are often marked noexcept so containers can optimize.
  • Prefer noexcept over old throw().
SyntaxMeaning
void f() noexceptWill not throw
void f()May throw
void f() throw() (old)Obsolete — avoid

Catching by value vs reference

  • Catching polymorphic exceptions **by value** can **slice** the object.
  • Always prefer catch (const exception& e).
  • Catch-all catch (...) cannot inspect the object — log and rethrow or translate.

Exception safety levels (overview)

  • **No-throw:** operation never throws (noexcept).
  • **Strong:** failed operation leaves the program state unchanged (commit-or-rollback).
  • **Basic:** no leaks; state may be partially updated but still valid.
  • Aim for at least basic safety; use RAII to avoid leaks.

When not to use exceptions

  • Do not use throw/catch for ordinary loops or expected frequent cases (e.g. end of input).
  • In performance-critical hot paths, prefer error codes / optional results if exceptions are too costly.
  • Constructors that fail can throw — that is idiomatic; partially constructed objects are cleaned via member dtors.

Real-life use cases

  • File open failure → throw / catch and show a user message.
  • Invalid form input → invalid_argument.
  • Network timeout → propagate to UI layer.
  • Out-of-range container access (vector::at) throws out_of_range.
L3

Tricky Interview Points

Q1) What is exception handling in C++?

Answer A mechanism using try, catch, and throw to handle runtime errors while keeping normal code separate from error-handling code.

Q2) What are the keywords used?

Answer try, catch, throw; plus noexcept (C++11). There is no finally — use RAII.

Q3) What happens if an exception is not caught?

Answer std::terminate() is called and the program aborts.

Q4) Why catch by const reference?

Answer Avoids copying and object slicing when catching polymorphic std::exception hierarchies.

Q5) What is rethrow?

Answer Inside a catch, bare throw; throws the current exception again to an outer handler.

Q6) Order of catch blocks?

Answer Most specific (derived) first, then base, then catch (...). Order matters.

Q7) What is noexcept?

Answer A specifier meaning the function does not throw; a throw from it calls terminate.

Q8) Is there a finally block in C++?

Answer No. Cleanup belongs in destructors (RAII).

Q9) Name some standard exception classes.

Answer exception, runtime_error, logic_error, invalid_argument, out_of_range, bad_alloc, overflow_error.

Q10) What is stack unwinding?

Answer Destroying automatic objects as the call stack is rolled back from throw site to the matching catch.

Q11) Should exceptions be used for normal control flow?

Answer No — reserve them for exceptional/error situations.

Q12) How do you create a custom exception?

Answer Define a class (preferably derived from std::exception or runtime_error) and throw an instance of it.

L4

Simple Programs

Division by zero — basic throw

#include <iostream>
using namespace std;

int main() {
    try {
        int a = 10, b = 0;
        if (b == 0) throw "Division by zero!";
        cout << a / b << "\n";
    } catch (const char* msg) {
        cout << "Exception: " << msg << "\n";
    }
    cout << "Continues\n";
    return 0;
}

Multiple catch + catch-all

#include <iostream>
using namespace std;

int main() {
    try {
        throw 42;
    } catch (int e) {
        cout << "int: " << e << "\n";
    } catch (...) {
        cout << "unknown\n";
    }
    return 0;
}

Nested try and rethrow

#include <iostream>
using namespace std;

int main() {
    try {
        try {
            throw 100;
        } catch (int e) {
            cout << "Inner: " << e << "\n";
            throw;
        }
    } catch (int e) {
        cout << "Outer: " << e << "\n";
    }
    return 0;
}

std::invalid_argument

#include <iostream>
#include <stdexcept>
using namespace std;

int main() {
    try {
        throw invalid_argument("must be positive");
    } catch (const exception& e) {
        cout << e.what() << "\n";
    }
    return 0;
}

Custom exception

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

class MyException : public runtime_error {
public:
    explicit MyException(const string& m) : runtime_error(m) {}
};

int main() {
    try {
        throw MyException("custom error");
    } catch (const MyException& e) {
        cout << e.what() << "\n";
    }
    return 0;
}

vector::at throws out_of_range

#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    try {
        cout << v.at(10) << "\n";
    } catch (const out_of_range& e) {
        cout << "Exception: " << e.what() << "\n";
    }
    return 0;
}
L5

Problem Solving Tricks

  • try / catch / throw are the core keywords.
  • Catch class exceptions as const T&.
  • Derived catch before base; catch (...) last.
  • No finally — use RAII destructors for cleanup.
  • Prefer noexcept over obsolete throw().
  • Bare throw; rethrows the current exception.
  • Uncaught exception → std::terminate().
  • Use exceptions for errors, not everyday branching.
  • e.what() reads standard exception messages.
  • Stack unwinding runs destructors — design with RAII.
  • Do not throw from destructors during unwinding.
  • invalid_argument / out_of_range / runtime_error cover many cases.
L6

Practice Programs List

  1. Write a divide(a,b) that throws on b==0 and catch the message in main.
  2. Demonstrate three catch blocks including catch (...).
  3. Nest try/catch and rethrow from the inner handler.
  4. Throw invalid_argument for a negative age; print e.what().
  5. Create MyException : runtime_error and catch it by const reference.
  6. Call vector::at with a bad index and handle out_of_range.
  7. Show wrong vs right catch order with a small exception hierarchy.
  8. Mark a helper noexcept and explain what happens if it throws.
  9. Open a file with ifstream; if fail, throw runtime_error.
  10. List five real apps where exception handling improves UX.
  11. Explain stack unwinding in comments next to a Trace RAII class.
  12. Refactor error-code style checks into try/catch for one function.