Unit 6.0: Exception Handling ⚠️
1. Introduction to Exception Handling
- An exception is an unexpected event or error that disrupts the normal flow of a program (e.g., dividing by zero, file not found, invalid input, out-of-memory).
Without Exception Handling (Bad Practice)
#include <iostream>
using namespace std;
// Old style: use return codes — messy and error-prone
int divide(int a, int b, int& result) {
if (b == 0) return -1; // error code
result = a / b;
return 0; // success
}
int main() {
int result;
int status = divide(10, 0, result);
if (status == -1) {
cout << "Error: division by zero" << endl;
}
// Caller must always check return value — easy to forget!
return 0;
}With Exception Handling (Clean)
#include <iostream>
#include <stdexcept>
using namespace std;
int divide(int a, int b) {
if (b == 0)
throw invalid_argument("Division by zero!");
return a / b;
}
int main() {
try {
cout << divide(10, 2) << endl; // 5 (ok)
cout << divide(10, 0) << endl; // throws!
} catch (const invalid_argument& e) {
cout << "Error: " << e.what() << endl;
}
cout << "Program continues normally." << endl;
return 0;
}Exception Handling Flow
Normal Flow:
try block → execute statements → (no exception) → continue after try-catch
Exception Flow:
try block → statement throws → remaining try skipped
→ matching catch block executes
→ program continues after the catch block
2. try, throw, and catch
The Three Keywords
| Keyword | Purpose |
|---|---|
try | Wraps code that might throw an exception |
throw | Creates and sends an exception |
catch | Handles a thrown exception |
Basic Syntax
try {
// Risky code
throw SomeExceptionType("message");
}
catch (const SomeExceptionType& e) {
// Handle it
cout << e.what() << endl;
}
catch (...) {
// Catch-all: handles ANY exception
cout << "Unknown exception caught!" << endl;
}Throwing Different Types
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
void checkAge(int age) {
if (age < 0 || age > 150)
throw out_of_range("Age must be between 0 and 150");
}
void checkDivision(double a, double b) {
if (b == 0.0)
throw invalid_argument("Divisor cannot be zero");
}
void openFile(const string& filename) {
if (filename.empty())
throw runtime_error("Filename cannot be empty");
// simulate file not found
if (filename == "missing.txt")
throw runtime_error("File not found: " + filename);
}
int main() {
// Test age check
try {
checkAge(25);
cout << "Age 25: OK" << endl;
checkAge(-5); // throws
} catch (const out_of_range& e) {
cout << "Range error: " << e.what() << endl;
}
// Test division
try {
checkDivision(10.0, 2.0);
cout << "Division: OK" << endl;
checkDivision(5.0, 0.0); // throws
} catch (const invalid_argument& e) {
cout << "Argument error: " << e.what() << endl;
}
// Test file
try {
openFile("missing.txt");
} catch (const runtime_error& e) {
cout << "Runtime error: " << e.what() << endl;
}
cout << "Program completed." << endl;
return 0;
}Throwing Primitive Types (Not Recommended)
#include <iostream>
using namespace std;
int main() {
// Can throw any type, but prefer exception classes
try {
throw 42; // throw int
} catch (int code) {
cout << "Error code: " << code << endl;
}
try {
throw "Something went wrong"; // throw string literal
} catch (const char* msg) {
cout << "Error: " << msg << endl;
}
// Better: use standard exception types (shown in section 3 & 6)
return 0;
}3. Creating Custom Exception Classes
You can create exception classes tailored to your application by inheriting from std::exception or any of its children.
Simple Custom Exception
#include <iostream>
#include <exception>
#include <string>
using namespace std;
// Custom exception: inherit from std::exception
class ValidationError : public exception {
string message;
public:
explicit ValidationError(const string& msg) : message(msg) {}
// Override what() — returns the error message
const char* what() const noexcept override {
return message.c_str();
}
};
void validateUsername(const string& name) {
if (name.length() < 3)
throw ValidationError("Username too short (min 3 chars)");
if (name.length() > 20)
throw ValidationError("Username too long (max 20 chars)");
for (char c : name) {
if (!isalnum(c) && c != '_')
throw ValidationError("Username contains invalid character: " + string(1, c));
}
}
int main() {
string users[] = {"ok", "valid_user", "ab", "has space", "good123"};
for (const string& user : users) {
try {
validateUsername(user);
cout << "\"" << user << "\" is VALID" << endl;
} catch (const ValidationError& e) {
cout << "\"" << user << "\" INVALID: " << e.what() << endl;
}
}
return 0;
}Exception Hierarchy
#include <iostream>
#include <exception>
#include <string>
using namespace std;
// Base application exception
class AppException : public exception {
protected:
string message;
int code;
public:
AppException(const string& msg, int code = 0)
: message(msg), code(code) {}
const char* what() const noexcept override { return message.c_str(); }
int getCode() const { return code; }
};
// Derived: database errors
class DatabaseException : public AppException {
string query;
public:
DatabaseException(const string& msg, const string& q, int code = 1000)
: AppException(msg, code), query(q) {}
string getQuery() const { return query; }
const char* what() const noexcept override {
// Return enhanced message
static string full;
full = "[DB Error " + to_string(code) + "] " + message + " | Query: " + query;
return full.c_str();
}
};
// Derived: network errors
class NetworkException : public AppException {
int statusCode;
public:
NetworkException(const string& msg, int status, int code = 2000)
: AppException(msg, code), statusCode(status) {}
int getStatusCode() const { return statusCode; }
};
// Derived: auth errors
class AuthException : public AppException {
public:
AuthException(const string& msg)
: AppException(msg, 3000) {}
};
void simulateDB() {
throw DatabaseException("Table not found", "SELECT * FROM nonexistent", 1042);
}
void simulateAuth() {
throw AuthException("Invalid credentials");
}
int main() {
// Catch specific types
try {
simulateDB();
} catch (const DatabaseException& e) {
cout << e.what() << endl;
cout << "Error code: " << e.getCode() << endl;
}
try {
simulateAuth();
} catch (const AuthException& e) {
cout << "Auth failed: " << e.what() << endl;
}
// Catch base class — handles any AppException
try {
simulateDB();
} catch (const AppException& e) {
cout << "App error [" << e.getCode() << "]: " << e.what() << endl;
}
return 0;
}4. Exception Handling Techniques
4.1 Terminate the Program
Used when the error is fatal and recovery is impossible. The program logs the error and exits cleanly.
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <cstdlib> // for exit()
using namespace std;
void logError(const string& msg) {
cerr << "[FATAL] " << msg << endl;
// Could write to log file here
ofstream log("error_log.txt", ios::app);
if (log.is_open()) {
log << "[FATAL] " << msg << endl;
}
}
int* allocateLargeBuffer(size_t size) {
int* buf = new(nothrow) int[size];
if (!buf) {
throw bad_alloc();
}
return buf;
}
int main() {
try {
// Critical initialization code
// allocateLargeBuffer(1000000000000ULL); // Would fail
cout << "System initialized successfully." << endl;
// ... rest of program ...
}
catch (const bad_alloc& e) {
logError("Out of memory: " + string(e.what()));
exit(EXIT_FAILURE); // terminate with error code
}
catch (const exception& e) {
logError("Fatal error: " + string(e.what()));
exit(EXIT_FAILURE);
}
catch (...) {
logError("Unknown fatal error!");
exit(EXIT_FAILURE);
}
return 0;
}4.2 Fix the Error and Continue
The exception is caught, corrected, and execution continues normally.
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class Config {
int maxRetries;
double timeout;
string logLevel;
public:
Config() : maxRetries(3), timeout(30.0), logLevel("INFO") {}
void setMaxRetries(int n) {
if (n <= 0)
throw invalid_argument("maxRetries must be positive");
maxRetries = n;
}
void setTimeout(double t) {
if (t <= 0)
throw invalid_argument("timeout must be positive");
timeout = t;
}
void setLogLevel(const string& level) {
if (level != "DEBUG" && level != "INFO" &&
level != "WARN" && level != "ERROR")
throw invalid_argument("Invalid log level: " + level);
logLevel = level;
}
void display() const {
cout << "Config: retries=" << maxRetries
<< " timeout=" << timeout
<< " level=" << logLevel << endl;
}
};
int main() {
Config cfg;
// Attempt to set values, fix invalid ones automatically
struct { string key; string value; } settings[] = {
{"retries", "5"},
{"retries", "-2"}, // invalid → fix to default
{"timeout", "60"},
{"timeout", "-10"}, // invalid → fix to default
{"loglevel", "VERBOSE"} // invalid → fix to default
};
for (auto& s : settings) {
try {
if (s.key == "retries") cfg.setMaxRetries(stoi(s.value));
if (s.key == "timeout") cfg.setTimeout(stod(s.value));
if (s.key == "loglevel") cfg.setLogLevel(s.value);
}
catch (const invalid_argument& e) {
cout << "Invalid value for " << s.key << " (\"" << s.value << "\")"
<< ": " << e.what() << " → keeping default." << endl;
// DON'T rethrow — just continue with default value
}
}
cfg.display();
return 0;
}4.3 Log the Error and Continue
The exception is caught, recorded in a log, and execution moves on to the next item.
#include <iostream>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <chrono>
#include <ctime>
using namespace std;
class Logger {
ofstream logFile;
bool toConsole;
public:
Logger(const string& filename = "", bool console = true)
: toConsole(console) {
if (!filename.empty()) {
logFile.open(filename, ios::app);
}
}
void log(const string& level, const string& msg) {
auto now = chrono::system_clock::now();
time_t t = chrono::system_clock::to_time_t(now);
string timeStr(ctime(&t));
timeStr.pop_back(); // remove newline
string entry = "[" + timeStr + "][" + level + "] " + msg;
if (toConsole) cout << entry << endl;
if (logFile.is_open()) logFile << entry << "\n";
}
void error(const string& msg) { log("ERROR", msg); }
void warn(const string& msg) { log("WARN", msg); }
void info(const string& msg) { log("INFO", msg); }
};
// Simulate processing orders
struct Order {
int id;
string product;
int quantity;
};
double processOrder(const Order& order) {
if (order.quantity <= 0)
throw invalid_argument("Quantity must be positive for order #" +
to_string(order.id));
if (order.product.empty())
throw runtime_error("Product name is empty for order #" +
to_string(order.id));
return order.quantity * 9.99; // price per unit
}
int main() {
Logger logger("", true); // console only for this demo
vector<Order> orders = {
{1001, "Widget", 5},
{1002, "", 3}, // invalid: empty product
{1003, "Gadget", -1}, // invalid: negative quantity
{1004, "Donut", 10},
{1005, "Gizmo", 0} // invalid: zero quantity
};
double totalRevenue = 0.0;
int processed = 0, errors = 0;
for (const Order& order : orders) {
try {
double revenue = processOrder(order);
totalRevenue += revenue;
processed++;
logger.info("Order #" + to_string(order.id) +
" processed. Revenue: $" + to_string(revenue));
}
catch (const invalid_argument& e) {
errors++;
logger.error(string(e.what()));
// Continue with next order
}
catch (const runtime_error& e) {
errors++;
logger.error(string(e.what()));
// Continue with next order
}
}
cout << "\n=== Summary ===" << endl;
cout << "Processed: " << processed << " | Errors: " << errors << endl;
cout << "Total Revenue: $" << totalRevenue << endl;
return 0;
}5. Stack Unwinding
When an exception is thrown, C++ unwinds the call stack — it goes back through each function call that led to the throw, calling destructors for local objects along the way.
Visualizing Stack Unwinding
#include <iostream>
#include <stdexcept>
using namespace std;
class Resource {
string name;
public:
Resource(string n) : name(n) {
cout << " [Acquired] " << name << endl;
}
~Resource() {
cout << " [Released] " << name << " (stack unwinding)" << endl;
}
void use() { cout << " [Using] " << name << endl; }
};
void functionC() {
Resource r3("DB Connection");
r3.use();
cout << " → functionC: about to throw!" << endl;
throw runtime_error("Something went wrong in C!");
// r3 destructor will be called during unwinding
}
void functionB() {
Resource r2("File Handle");
r2.use();
cout << "→ functionB: calling C" << endl;
functionC(); // exception propagates here
// r2 destructor will be called during unwinding
cout << "→ functionB: after C (never reached)" << endl;
}
void functionA() {
Resource r1("Memory Buffer");
r1.use();
cout << "→ functionA: calling B" << endl;
functionB(); // exception propagates here
// r1 destructor will be called during unwinding
cout << "→ functionA: after B (never reached)" << endl;
}
int main() {
cout << "=== Stack Unwinding Demo ===" << endl;
try {
functionA();
}
catch (const runtime_error& e) {
cout << "\nCaught in main: " << e.what() << endl;
}
cout << "\nProgram continues after exception." << endl;
return 0;
}
/*
Output:
=== Stack Unwinding Demo ===
[Acquired] Memory Buffer
[Using] Memory Buffer
→ functionA: calling B
[Acquired] File Handle
[Using] File Handle
→ functionB: calling C
[Acquired] DB Connection
[Using] DB Connection
→ functionC: about to throw!
[Released] DB Connection (stack unwinding)
[Released] File Handle (stack unwinding)
[Released] Memory Buffer (stack unwinding)
Caught in main: Something went wrong in C!
Program continues after exception.
*/Stack Unwinding Guarantees (RAII)
RAII (Resource Acquisition Is Initialization) is the principle that resources are automatically released when a variable goes out of scope — even during exception unwinding.
#include <iostream>
#include <fstream>
#include <stdexcept>
using namespace std;
// RAII wrapper for a mutex-like lock
class ScopedLock {
string resource;
public:
explicit ScopedLock(const string& r) : resource(r) {
cout << "Locked: " << resource << endl;
}
~ScopedLock() {
// ALWAYS runs — even when exception thrown!
cout << "Unlocked: " << resource << endl;
}
};
void riskyOperation(bool fail) {
ScopedLock lock("SharedFile"); // lock is acquired
cout << "Doing work..." << endl;
if (fail) {
throw runtime_error("Operation failed!");
}
cout << "Work done." << endl;
// lock released here normally, OR during unwinding if exception thrown
}
int main() {
cout << "--- Success case ---" << endl;
try {
riskyOperation(false);
} catch (const exception& e) {
cout << "Caught: " << e.what() << endl;
}
cout << "\n--- Failure case ---" << endl;
try {
riskyOperation(true);
} catch (const exception& e) {
cout << "Caught: " << e.what() << endl;
}
// Lock is ALWAYS released — no resource leak!
return 0;
}6. Standard Exception Classes
C++ provides a rich hierarchy of standard exception classes.
std::exception
│
├── std::logic_error (detectable before runtime)
│ ├── std::invalid_argument (invalid argument passed)
│ ├── std::domain_error (math domain error)
│ ├── std::length_error (exceeds max allowed size)
│ └── std::out_of_range (access out of valid range)
│
└── std::runtime_error (only detectable at runtime)
├── std::overflow_error (arithmetic overflow)
├── std::underflow_error (arithmetic underflow)
├── std::range_error (result out of range)
└── std::bad_alloc (memory allocation failed)
Using Standard Exceptions
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
class SafeVector {
vector<int> data;
public:
void add(int val) {
data.push_back(val);
}
int get(int index) const {
if (index < 0 || index >= (int)data.size())
throw out_of_range("Index " + to_string(index) +
" out of range [0, " + to_string(data.size()-1) + "]");
return data[index];
}
int size() const { return data.size(); }
};
double safeSqrt(double x) {
if (x < 0)
throw domain_error("Square root of negative number: " + to_string(x));
return sqrt(x);
}
void processInput(const string& input) {
if (input.empty())
throw invalid_argument("Input string cannot be empty");
// process...
}
int main() {
// out_of_range
SafeVector sv;
sv.add(10); sv.add(20); sv.add(30);
try {
cout << sv.get(1) << endl; // 20 — ok
cout << sv.get(5) << endl; // throws
} catch (const out_of_range& e) {
cout << "out_of_range: " << e.what() << endl;
}
// domain_error
try {
cout << safeSqrt(16) << endl; // 4 — ok
cout << safeSqrt(-9) << endl; // throws
} catch (const domain_error& e) {
cout << "domain_error: " << e.what() << endl;
}
// invalid_argument
try {
processInput("hello"); // ok
processInput(""); // throws
} catch (const invalid_argument& e) {
cout << "invalid_argument: " << e.what() << endl;
}
// bad_alloc
try {
// Attempt to allocate an impossibly large amount
// vector<int> v(1000000000000LL); // bad_alloc
} catch (const bad_alloc& e) {
cout << "bad_alloc: out of memory" << endl;
}
return 0;
}7. Multiple catch Blocks
You can have multiple catch blocks to handle different exception types differently.
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
void riskyFunction(int mode) {
switch (mode) {
case 1: throw invalid_argument("Invalid argument provided");
case 2: throw out_of_range("Value out of allowed range");
case 3: throw runtime_error("Runtime failure occurred");
case 4: throw logic_error("Logic error in calculation");
case 5: throw 42; // int exception (unusual)
case 6: throw string("string error"); // string exception (unusual)
}
}
int main() {
for (int mode = 0; mode <= 7; mode++) {
try {
cout << "Mode " << mode << ": ";
riskyFunction(mode);
cout << "No exception." << endl;
}
catch (const invalid_argument& e) {
cout << "[invalid_argument] " << e.what() << endl;
}
catch (const out_of_range& e) {
cout << "[out_of_range] " << e.what() << endl;
}
catch (const runtime_error& e) {
cout << "[runtime_error] " << e.what() << endl;
}
catch (const logic_error& e) {
// catches any logic_error (and its children)
cout << "[logic_error] " << e.what() << endl;
}
catch (const exception& e) {
// catches any std::exception
cout << "[exception] " << e.what() << endl;
}
catch (int code) {
cout << "[int thrown] " << code << endl;
}
catch (const string& s) {
cout << "[string thrown] " << s << endl;
}
catch (...) {
// catch-all: handles ANYTHING
cout << "[unknown exception]" << endl;
}
}
return 0;
}Order matters! Always catch more specific types before less specific types.
Correct order:
invalid_argument→logic_error→exceptionWrong order:exceptionfirst would swallow everything!
8. Re-throwing Exceptions
Sometimes you catch an exception to do partial handling (e.g., logging), then re-throw it for a higher-level handler.
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class TransactionLogger {
public:
static void logFailure(const string& context, const exception& e) {
cerr << "[LOG] Transaction failed in " << context
<< ": " << e.what() << endl;
}
};
void processPayment(double amount) {
if (amount <= 0)
throw invalid_argument("Payment amount must be positive");
if (amount > 10000)
throw out_of_range("Payment exceeds daily limit of $10,000");
cout << "Payment of $" << amount << " processed." << endl;
}
void handlePayment(double amount) {
try {
processPayment(amount);
}
catch (const exception& e) {
// Partial handling: log it
TransactionLogger::logFailure("handlePayment", e);
// Re-throw to let caller decide what to do
throw; // re-throw the SAME exception (not a copy)
}
}
int main() {
// Test case 1: valid
try {
handlePayment(500.0);
}
catch (const exception& e) {
cout << "Payment failed: " << e.what() << endl;
}
// Test case 2: negative
try {
handlePayment(-100.0);
}
catch (const invalid_argument& e) {
cout << "Invalid payment: " << e.what() << endl;
}
// Test case 3: too large
try {
handlePayment(15000.0);
}
catch (const out_of_range& e) {
cout << "Limit exceeded: " << e.what() << endl;
}
return 0;
}9. noexcept Specifier
noexcept marks a function as guaranteed not to throw. It improves performance and enables compiler optimizations.
#include <iostream>
#include <stdexcept>
using namespace std;
// Guaranteed not to throw — compiler can optimize
double add(double a, double b) noexcept {
return a + b;
}
// May throw
double safeDivide(double a, double b) {
if (b == 0.0)
throw invalid_argument("Division by zero");
return a / b;
}
class MyClass {
int* data;
int size;
public:
MyClass(int s) : size(s), data(new int[s]) {}
// Move constructor: should never throw
MyClass(MyClass&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Move assignment: should never throw
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~MyClass() noexcept { delete[] data; } // destructors should always be noexcept
};
// noexcept(expr) — conditionally noexcept
template <typename T>
void swapValues(T& a, T& b) noexcept(noexcept(T(move(a)))) {
T temp = move(a);
a = move(b);
b = move(temp);
}
int main() {
cout << add(3.0, 4.0) << endl; // 7
try {
cout << safeDivide(10.0, 2.0) << endl; // 5
cout << safeDivide(10.0, 0.0) << endl; // throws
} catch (const invalid_argument& e) {
cout << e.what() << endl;
}
// Check if a function is noexcept
cout << noexcept(add(1.0, 2.0)) << endl; // 1 (true)
cout << noexcept(safeDivide(1.0, 2.0)) << endl; // 0 (false)
return 0;
}10. Practical Examples
Example 1: Safe File Reader
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
class FileReader {
string filename;
public:
explicit FileReader(const string& fname) : filename(fname) {}
vector<string> readLines() const {
ifstream file(filename);
if (!file.is_open())
throw runtime_error("Cannot open file: " + filename);
vector<string> lines;
string line;
while (getline(file, line)) {
lines.push_back(line);
}
if (lines.empty())
throw runtime_error("File is empty: " + filename);
return lines;
}
};
int main() {
vector<string> filenames = {"data.txt", "missing.txt", "config.txt"};
for (const string& fname : filenames) {
try {
FileReader reader(fname);
auto lines = reader.readLines();
cout << fname << ": " << lines.size() << " lines read." << endl;
}
catch (const runtime_error& e) {
cout << "[Error] " << e.what() << " — skipping." << endl;
}
}
return 0;
}Example 2: Bank Transaction System
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
// Custom exceptions for banking
class BankException : public exception {
protected:
string msg;
public:
explicit BankException(const string& m) : msg(m) {}
const char* what() const noexcept override { return msg.c_str(); }
};
class InsufficientFundsException : public BankException {
double available, requested;
public:
InsufficientFundsException(double avail, double req)
: BankException("Insufficient funds"),
available(avail), requested(req) {
msg = "Insufficient funds: requested $" + to_string(req)
+ " but only $" + to_string(avail) + " available";
}
double getShortfall() const { return requested - available; }
};
class AccountLockedException : public BankException {
public:
explicit AccountLockedException()
: BankException("Account is locked. Contact customer support.") {}
};
class BankAccount {
double balance;
bool locked;
string owner;
int failedAttempts;
public:
BankAccount(string name, double initial)
: owner(name), balance(initial), locked(false), failedAttempts(0) {}
void deposit(double amount) {
if (locked) throw AccountLockedException();
if (amount <= 0)
throw invalid_argument("Deposit amount must be positive");
balance += amount;
cout << "Deposited $" << amount << ". Balance: $" << balance << endl;
}
void withdraw(double amount) {
if (locked) throw AccountLockedException();
if (amount <= 0)
throw invalid_argument("Withdrawal amount must be positive");
if (amount > balance)
throw InsufficientFundsException(balance, amount);
balance -= amount;
cout << "Withdrew $" << amount << ". Balance: $" << balance << endl;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount acc("Alice", 1000.0);
// Normal operations
try {
acc.deposit(500.0);
acc.withdraw(200.0);
acc.withdraw(2000.0); // throws InsufficientFunds
}
catch (const InsufficientFundsException& e) {
cout << "Error: " << e.what() << endl;
cout << "Shortfall: $" << e.getShortfall() << endl;
}
catch (const invalid_argument& e) {
cout << "Invalid: " << e.what() << endl;
}
catch (const BankException& e) {
cout << "Bank error: " << e.what() << endl;
}
cout << "Final balance: $" << acc.getBalance() << endl;
return 0;
}Quick Reference Summary
Exception Handling Template
try {
// Code that might throw
throw ExceptionType("message");
}
catch (const SpecificException& e) {
// Handle specific type
cout << e.what();
}
catch (const BaseException& e) {
// Handle base type (more general)
}
catch (...) {
// Handle anything else
}Creating a Custom Exception
class MyException : public exception {
string message;
public:
explicit MyException(const string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};Key Rules Cheat Sheet
| Rule | Detail |
|---|---|
| Catch by const reference | catch(const MyEx& e) — avoids slicing and copying |
| Order of catch blocks | Most specific first, most general last |
Always inherit from std::exception | Custom exceptions must have what() |
noexcept on destructors | Destructors should never throw |
noexcept on move ops | Move constructor/assignment should be noexcept |
Re-throw with throw; | Re-throws same exception (not throw e;) |
| RAII | Use destructors to release resources — safe under unwinding |
Catch-all catch(...) | Always last — use for logging/cleanup |