C++ exception handling gives a program a structured way to report and respond to unusual runtime failures using try, throw, and catch.
This lesson follows OSC++.014: File Input and Output Basics. File I/O introduced operations that can fail at runtime. Exception handling gives us another tool for separating normal work from exceptional error paths.
What is an exception?
An exception is an object or value used to report an exceptional condition. Instead of forcing every function to return an error code, a function can throw an exception. Control then searches for a matching catch handler.
Exceptions are not intended to replace every ordinary condition check. A predictable user choice, an empty vector, or a simple loop condition may be handled normally. Exceptions are useful when normal execution cannot continue cleanly at the point where the problem is detected.
The three core keywords
try— surrounds code that may throw.throw— reports an exceptional condition.catch— handles a matching exception.
#include <iostream>
#include <stdexcept>
int main() {
try {
int voltage = 0;
if (voltage == 0) {
throw std::runtime_error("voltage cannot be zero here");
}
std::cout << "Voltage: " << voltage << '\n';
}
catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << '\n';
}
}
The throw transfers control out of the try block. The matching catch receives the exception and can inspect its message with what().
Video 1: University introduction to try, throw, and catch
Throw standard exception types
The standard library provides exception classes such as std::runtime_error, std::invalid_argument, std::out_of_range, and others. These classes derive from std::exception and provide a what() message.
#include <stdexcept>
double amps(double watts, double volts) {
if (volts == 0.0) {
throw std::invalid_argument("volts must not be zero");
}
return watts / volts;
}
This function cannot calculate current when voltage is zero. It reports the invalid argument to the caller instead of silently returning a meaningless result.
Catch by const reference
A common pattern is to catch standard exceptions by const reference:
try {
double current = amps(3200.0, 0.0);
}
catch (const std::invalid_argument& e) {
std::cerr << e.what() << '\n';
}
Catching by reference avoids copying the exception object and preserves its dynamic type.
Multiple catch handlers
A try block can be followed by multiple handlers. Put more specific exception types before more general ones.
try {
// code that may throw
}
catch (const std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << '\n';
}
catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << '\n';
}
catch (const std::exception& e) {
std::cerr << "Standard exception: " << e.what() << '\n';
}
Video 2: College-level exception handling and input validation
Exceptions and file I/O
Our previous lesson checked stream state manually. File streams can also be configured to throw std::ios_base::failure when selected error states occur.
#include <fstream>
#include <iostream>
int main() {
try {
std::ifstream in;
in.exceptions(std::ifstream::failbit | std::ifstream::badbit);
in.open("scores.txt");
int score{};
in >> score;
}
catch (const std::ios_base::failure& e) {
std::cerr << "File I/O error: " << e.what() << '\n';
}
}
This does not mean exceptions are always better than stream-state checks. The important lesson is that a program should choose a deliberate error-handling strategy and apply it consistently.
Stack unwinding
When an exception leaves a function, C++ begins stack unwinding. Local automatic objects whose lifetimes have begun are destroyed as control moves outward looking for a matching handler. This behavior is one reason RAII—resource acquisition is initialization—is so important in modern C++.
If a file, lock, or memory resource is owned by an object whose destructor releases it, stack unwinding can clean that resource up automatically. Manual resource handling with raw pointers or C-style handles is much easier to get wrong.
Video 3: CppCon explains what happens under the hood
Rethrowing an exception
Sometimes a function can record context but cannot fully handle the problem. A bare throw; inside a handler rethrows the current exception.
try {
// work that may fail
}
catch (const std::exception& e) {
std::cerr << "Logging: " << e.what() << '\n';
throw;
}
Catch-all handlers
catch (...) catches any exception type. It can be useful at a top-level boundary, but it provides no direct access to the exception object unless you rethrow and inspect it elsewhere. Do not use a catch-all merely to hide failures.
Do not use exceptions for ordinary control flow
A loop ending, a menu selection, or a routine boolean condition usually does not need an exception. Exceptions make the most sense when normal execution cannot continue at the point of detection and the caller is better positioned to decide what to do.
Data-center software example
Imagine a small C++ utility that reads a miner configuration file. A low-level parser discovers an invalid numeric field. It can throw an exception describing the invalid input. A higher-level command-line layer can catch the exception, show a clear error to the technician, log the filename, and stop without continuing with corrupted configuration data.
Common beginner mistakes
- Throwing exceptions for normal loop or menu flow.
- Catching every exception and ignoring it.
- Catching a base exception before a more specific derived exception.
- Throwing raw pointers or unrelated values when a standard exception type would communicate intent better.
- Forgetting that uncaught exceptions terminate the program.
- Assuming
catch (...)automatically explains what went wrong. - Using manual resource cleanup that can be skipped during stack unwinding.
Practice
- Write a function that throws
std::invalid_argumentwhen a denominator is zero. - Catch that exception by const reference.
- Print
e.what(). - Add a second catch for
std::exception. - Explain why the more specific handler should come first.
- Modify a file-reading example so an open failure is reported clearly.
Previous C++ lessons
OSC++.014: File Input and Output Basics
OSC++.013: Strings and Text Basics
Reference
Microsoft Learn’s modern C++ exception and error-handling guidance explains when exceptions are appropriate and how they interact with robust software design.
Key takeaway
Use try around code that may throw, throw to report an exceptional condition, and a matching catch to handle it. Prefer meaningful standard exception types, catch by const reference, and design resource ownership so stack unwinding remains safe.

Leave a comment