S4.0 What This Teaches
Exceptions separate error detection from error handling. This tutorial covers:
try, catch, throw
- The standard exception hierarchy
- Catching by const reference
- Defining custom exception types
noexcept and when to use it
S4.1 try, catch, throw
double safe_divide(double a, double b) {
if (b == 0.0)
throw std::domain_error("division by zero");
return a / b;
}
try {
double r = safe_divide(10.0, 0.0);
} catch (const std::domain_error& e) {
std::cout << "caught: " << e.what() << "\n";
}
throw unwinds the call stack until a matching catch
is found. Destructors of all local objects are called along the way (stack
unwinding) - this is why RAII works with exceptions.
S4.2 Standard Exception Hierarchy
| Exception | When thrown by |
| std::exception | Base class; provides what() |
| std::runtime_error | Errors detected at runtime |
| std::logic_error | Errors in program logic (bugs) |
| std::invalid_argument | Bad argument value |
| std::out_of_range | vector::at, string::at, etc. |
| std::bad_alloc | new fails to allocate memory |
| std::domain_error | Mathematical domain errors |
Catch by const std::exception& to catch any standard exception.
Order catch clauses from most specific to least specific.
S4.3 Custom Exception Types
class ValidationError : public std::runtime_error {
public:
explicit ValidationError(const std::string& msg)
: std::runtime_error(msg) {}
};
int parse_positive(const std::string& s) {
int n = std::stoi(s);
if (n <= 0)
throw ValidationError("value must be positive, got: " + s);
return n;
}
try {
parse_positive("-5");
} catch (const ValidationError& e) {
std::cout << "validation: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "other error: " << e.what() << "\n";
}
S4.4 noexcept
int add(int a, int b) noexcept { return a + b; }
// move constructors should be noexcept
class Buffer {
public:
Buffer(Buffer&& other) noexcept : data_(other.data_) {
other.data_ = nullptr;
}
private:
int* data_;
};
noexcept tells the compiler the function will not throw. If it
does throw, std::terminate is called. Use it on move constructors,
move assignment operators, and simple utility functions. The standard library
uses noexcept to enable optimizations.
S4.5 Example - All Together
// Exceptions - try/catch/throw, standard hierarchy, custom exceptions.
#include <iostream>
#include <stdexcept>
#include <string>
class ValidationError : public std::runtime_error {
public:
explicit ValidationError(const std::string& msg)
: std::runtime_error(msg) {}
};
int parse_positive(const std::string& s) {
int n = std::stoi(s);
if (n <= 0) throw ValidationError("must be positive, got: " + s);
return n;
}
int main() {
for (const auto& input : {"42", "-5", "abc"}) {
try {
std::cout << input << " -> " << parse_positive(input) << "\n";
} catch (const ValidationError& e) {
std::cout << "validation: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "error: " << e.what() << "\n";
}
}
return 0;
}
42 -> 42
validation: must be positive, got: -5
error: stoi
S4.6 Exercise
Exercise
- Write a
Stack<T> class that throws
std::underflow_error on pop() or
top() when empty.
- Wrap
std::stod in a function that catches
std::invalid_argument and returns a
std::optional<double> instead of throwing.
- Mark a simple arithmetic function
noexcept and verify
with noexcept(f(1,2)) that the expression is noexcept.
S4.7 Common Mistakes
Catching by value instead of reference
catch (std::exception e) { } // slices: e loses the derived type information
catch (const std::exception& e) { } // correct
Catching ... without re-throwing in destructors
If an exception is already propagating and a destructor throws another
exception, std::terminate is called. Keep destructors
noexcept.
Using exceptions for normal control flow
Exception handling has overhead. Do not use throw/catch
to implement expected branching (e.g., end-of-loop detection). Reserve
exceptions for genuinely exceptional conditions.
S4.8 Key Terms
| Term | Meaning |
| throw | Raises an exception; begins stack unwinding |
| try | Block that may contain throwing code |
| catch | Handles a specific exception type |
| stack unwinding | Destroying local objects as the call stack is popped after a throw |
| what() | Member of std::exception; returns the error message |
| noexcept | Declares a function will not throw; enables compiler optimizations |
| std::terminate | Called when an exception escapes a noexcept function or a destructor throws |