Allow to pass errors back to the caller without returning error codes
They permit to decouple error handling from the regular control flow
Exceptions deal with anticipated, but uncommon cases, eg.:
If something should never happen, use assertions instead
If a function cannot reasonably deal with an error
main(.)
has a chance to catch the errorstd::terminate()
) with an
exception errorWhen recovering from an error is not straight forward in the current code unit
std::exit(ERROR_CODE)
is
not user friendly)return
does not work in constructorsSee eg. Google C++ Style Guide
Avoid unhandled exception at all costs
If a function cannot deal with a problem, it may use the
throw
statement
This is also commonly called "raising an exception"
throw std::runtime_error("Description of what went wrong.");
You may throw
string
literalsstd::exception
)std::exception
In a function directly or indirectly calling another function that uses
throw
try { // observe if any of the following statements throw an exception
// Code that might throw an exception
} catch ( const MostSpecificException& e ) {
// handle custom exception
} catch ( const LessSpecificException& e ) {
// handle custom exception
} catch ( const std::exception& e ) {
// handle all other standard exceptions
} catch ( ... ) {
std::cerr << e.what() << std::endl;
// Handle everything else (catch-all handler)
}
Deriving from std::exception
ensures a consistent
interface
class YourException : public std::exception {
public:
const char* what() const noexcept {
return "My exception occurred!";
}
};
noexcept
specifierFunctions are either non-throwing or potentially throwing
Functions can be defined as non-throwing using the noexcept
specifier
void f() noexcept;
void f() noexcept {
// ...
}
noexcept
is not enforced
at compile time
noexcept
specifier
noexcept
when exiting a function because of
throw is impossible [...]
noexcept
==
symmetric with respect to operand types and
noexcept
struct S {
uint64_t id;
double value;
};
bool operator==(const S& a, const S& b) noexcept {
return a.name == b.name && a.number == b.number;
}
main(.)
Catching All Exceptions#include <iostream>
void do_something() {
// code, that deep in the call stack might throw an unexpected exception
throw 1;
}
int main() {
// imagine a local variable that needs to be cleaned up
try {
do_something();
}
catch(...) {
std::cerr << "Abnormal termination\n"; // shouldn't happen
}
// now we can be certain that stack unwinding takes place
return 0;
}
To help your imagination ...
#include <iostream>
class C {
public:
C() { std::cout << "creating a temporary file (just pretend)" << std::endl; }
~C() { std::cout << "removing a temporary file (just pretend)" << std::endl; }
};
void do_something() {
// code, that deep in the call stack might throw an unexpected exception
throw 1;
}
int main() {
C c;
try {
do_something();
}
catch(...) {
std::cerr << "Abnormal termination\n"; // shouldn't happen
}
// now we can be certain that stack unwinding takes place
return 0;
}
Instead, with uncaught exceptions (which should really be avoided) ...
#include <iostream>
class C {
public:
C() { std::cout << "creating a temporary file (just pretend)" << std::endl; }
~C() { std::cout << "removing a temporary file (just pretend)" << std::endl; }
};
void do_something() {
// code, that deep in the call stack might throw an unexpected exception
throw 1;
}
int main() {
C c;
do_something();
// orderly stack unwinding is not guaranteed (depends on implementation)
return 0;
}
#include <fstream>
#include <iostream>
#include <stdexcept>
void read_file(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filename);
}
// ...
}
int main() {
try {
read_file("file_io_error.cpp"); // file might exist
read_file("missing_file"); // exception is thrown
} catch ( const std::runtime_error& e ) {
std::cout << e.what() << std::endl;
} catch ( ... ) {
std::cout << "something terrible happened" << std::endl;
}
return 0;
}
Download file_io_error.cpp
#include <iostream>
#include <sstream>
#include <string>
class InvalidCircleException : std::exception {
public:
InvalidCircleException(double radius) : message_{create_message(radius)} {};
const char* what() const noexcept override {
return message_.c_str();
}
private:
std::string create_message(double radius) {
std::stringstream message;
message << "A circle must not have a negative radius (";
message << radius << ")!";
return message.str();
}
std::string message_;
};
class Circle {
public:
Circle(double x, double y, double _radius) : x_{x}, y_{y} { radius(_radius); }
double x() const { return x_; }
double y() const { return y_; }
double radius() const { return radius_; }
void radius(double radius) {
if (radius < 0) {
throw InvalidCircleException(radius);
}
radius_ = radius;
}
private:
double x_{0.0};
double y_{0.0};
double radius_{0.0};
};
std::ostream& operator<<(std::ostream& out, const Circle& c) {
out << "Circle(x=" << c.x() << ", y=" << c.y() << ", radius=" << c.radius();
out << ")";
return out;
}
int main() {
try {
Circle a{25, 15, 7}; // ok
std::cout << a << std::endl;
Circle invalid{25, 15, -7}; // throws
std::cout << invalid << std::endl;
} catch ( const InvalidCircleException& e ) {
std::cout << e.what() << std::endl;
} catch ( ... ) {
std::cout << "something terrible happened" << std::endl;
}
return 0;
}
Download invalid_circle.cpp