पाठ 19 / 25
Exceptions, noexcept and std::expected
Choose between exceptions and error values, and write exception-safe code.
Reporting failure
C++ supports exceptions: throw an object (usually derived from std::exception, such as std::invalid_argument or std::runtime_error), and try/catch it higher up, catching by const reference (catch (const std::exception& e)). During stack unwinding, destructors of all automatic objects run, which is why RAII makes exception handling safe. Exception-safety guarantees describe functions: no-throw (marked noexcept, important for move constructors and destructors, which containers rely on), strong (an operation either succeeds or leaves state unchanged) and basic (no leaks, valid state). Exceptions suit rare, truly exceptional failures. For expected failures such as parsing user input, many codebases return values instead: std::optional when there is no detail, and std::expected<T, E> (C++23) to return either a value or an error. Some domains, such as games and embedded systems, compile with exceptions disabled and use error codes throughout. Whatever the style, be consistent within a codebase and never let errors pass silently.
Stack unwinding
When an exception is thrown, frames are unwound and their destructors run until a matching handler is found.
Exceptions for exceptional cases, expected for expected ones
Both styles side by side.
#include <expected>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <string>
std::string read_config(const std::string& path) {
std::ifstream in(path);
if (!in) throw std::runtime_error("cannot open " + path); // exceptional: config must exist
return {std::istreambuf_iterator<char>(in), {}};
}
enum class ParseError { Empty, NotANumber, OutOfRange };
std::expected<int, ParseError> parse_age(const std::string& s) { // expected failures
if (s.empty()) return std::unexpected(ParseError::Empty);
int v = 0;
for (char c : s) {
if (c < '0' || c > '9') return std::unexpected(ParseError::NotANumber);
v = v * 10 + (c - '0');
if (v > 150) return std::unexpected(ParseError::OutOfRange);
}
return v;
}
int main() {
try {
auto cfg = read_config("app.conf");
} catch (const std::exception& e) {
// log e.what() and exit with an error code
}
if (auto age = parse_age("34"); age) { /* *age == 34 */ }
else if (age.error() == ParseError::NotANumber) { /* tell the user */ }
}Mark move operations noexcept
std::vector uses a type's move constructor during reallocation only if it is noexcept; otherwise it copies to preserve the strong guarantee. Forgetting noexcept on moves can silently make code much slower.
त्वरित जाँच: How should exceptions usually be caught in C++?
- By const reference
- By value
- By pointer
- With catch(...) only
Answer
By const reference — Catching by const reference avoids slicing and unnecessary copies.