Exceptions & File I/O
try/catch, custom exception types, and reading/writing files with fstream — tied together by RAII.
Beginner
throw unwinds the stack, destroying every local object along the way
(running their destructors), until it finds a matching catch. You can throw any
type, but in practice always throw something derived from std::exception
— it gives callers a uniform .what() to read, and lets a single
catch (const std::exception &) handle every well-behaved exception in the
codebase.
#include <stdexcept>
double divide(double a, double b) {
if (b == 0.0) throw std::invalid_argument("divide by zero");
return a / b;
}
try {
divide(1.0, 0.0);
} catch (const std::invalid_argument &e) {
std::cout << "caught: " << e.what() << '\n';
} catch (const std::exception &e) {
std::cout << "some other standard exception: " << e.what() << '\n';
} catch (...) {
std::cout << "caught something not derived from std::exception\n";
}
catch (...) as a last resort if you
want one. Catching by const& (not by value) avoids slicing a derived
exception type down to its base.
class ParseError : public std::runtime_error {
public:
explicit ParseError(const std::string &msg) : std::runtime_error(msg) {}
};
throw ParseError("unexpected token at line 12");
// caught either as ParseError specifically, or generically as std::exception
ScopeTracer local object in each frame to make stack unwinding visible:
TopNotchNote/cpp/exceptions_custom_parseerror_demo.cpp
void f() throw(int); listing exactly which types a function
may throw) were deprecated in C++11 and removed entirely in C++17 — the compiler no
longer enforces them and some compilers reject the syntax outright. The one surviving,
actively-used form is the unparameterized noexcept below.
noexcept is both a specifier and an operator, and they answer different
questions. As a specifier on a function, it's a promise that the function
won't let an exception escape — if one does anyway, std::terminate is
called immediately rather than unwinding normally. As an operator,
noexcept(expr) is a compile-time check that returns true if
expr is declared not to throw.
void logMessage(const std::string &s) noexcept { // promise: this never throws
std::cout << s << '\n';
}
static_assert(noexcept(logMessage("x")), "expected logMessage to be noexcept");
noexcept whenever they genuinely can't throw. Containers
like std::vector check this at compile time — if a type's move constructor
isn't noexcept, growing the vector falls back to copying elements
during reallocation instead of moving them, specifically so that a mid-reallocation exception
can't leave the vector in a corrupted, half-moved state.
<fstream> gives you ifstream (input), ofstream
(output), and fstream (both) — each is RAII: the file closes automatically
when the stream object goes out of scope, so an explicit .close() is rarely
necessary, just good documentation of intent.
std::ofstream out("results.txt");
out << "index,value\n";
out << 1 << ',' << 3.14 << '\n';
// closes automatically when out goes out of scope
std::ifstream in("results.txt");
std::string header;
std::getline(in, header); // read one line
int index; double value; char comma;
while (in >> index >> comma >> value) { // >> stops at whitespace, so ',' needs its own read
std::cout << index << " -> " << value << '\n';
}
std::ifstream in("maybe_missing.txt");
if (!in) { // check 1: did the open succeed?
std::cerr << "failed to open file\n";
}
std::ifstream strict;
strict.exceptions(std::ifstream::failbit | std::ifstream::badbit); // check 2: opt into throwing instead
try {
strict.open("maybe_missing.txt");
} catch (const std::ios_base::failure &e) {
std::cerr << "open failed: " << e.what() << '\n';
}
unique_ptr deleting itself, a mutex
unlocking itself when a lock_guard goes out of scope — all the same idea:
Resource Acquisition Is Initialization. Tie a resource's lifetime to a stack
object's constructor/destructor, and the resource is released correctly on every exit path
— a normal return, an early return, or an exception unwinding the stack — without
a single explicit cleanup call. It's the reason exceptions and manual resource management
coexist safely in C++: an exception unwinding the stack still runs every local object's
destructor on the way out.