C++ Exceptions
Exceptions are exceptional conditions. Exception handling manages situations that otherwise would make the system crash or misbehave. Exceptions are classes that are thown or returned from code inside a try
block to be dealt in a catch
block.
This is the basic structure of exception handling for handling a generic object of class exception
:
#include <exception> try { // ... throw exception(); // construct and exception object // ... } catch (exception & ex) { cout << "exception bearing message: \"" << ex.what() << "\" has been thrown..." << endl; } // code resumes here
Class std::exception
Class exception
is declared in <exception>. It cannot be initialized with a string message. Thus, to handle an exception in a catch
clause, you may use the exception
interface. The interface of all standard exceptions classes contains only one member that can be used to get additional information besides the type itself: the member function what()
, which returns a null-terminated byte string. The content of the string is implementation defined. The C-string, returned by what()
, is valid until the exception object from which it is obtained gets destroyed.
#include <exception> class exception { // since C++11 // all member functions are constexpr // since C++26 public: exception& operator=(const exception& other) noexcept; // since C++11 // constexpr since C++26 virtual const char* what() const noexcept; // // constexpr since C++26 exception() noexcept; exception(const exception& other) noexcept; };
(The remaining members of the standard exception classes create, copy, assign, and destroy exception objects. Note that besides what()
there is no additional member for any of the standard exception classes that describes the kind of exception. For example, there is no portable way to find out the context of an exception or the faulty index of a range error. Thus, a portable evaluation of an exception could only print the message returned from, well, what()
.)
Exception Action
An exception thrown transfers control to the first matching catch block after all objects constructed in the try block have been deleted.
The exception
classes
These are the exception classes, derived from class exception
:
bad_alloc
: thrown whenever the global operatornew
fails (except when thenothrow
version of new is used). This is probably the most important exception because it might occur at any time in any nontrivial program.bad_cast
: thrown by thedynamic_cast
operator if a type conversion on a reference fails at runtime.bad_typeid
: thrown by thetypeid
operator for runtime type identification. If the argument to typeid is zero or the null pointer-
logic_error
:-
domain_error
: used to report a domain error. The domain here is the subset of values that a function is defined for, such as positive numbers for the square root functions.No component of the standard library throws exceptions of this type. It is designed as a standard exception to be thrown by programs.
invalid_argument
: used to report invalid arguments, such as when a bitset (array of bits) is initialized with a char other than '0' or '1'.length_error
: used to report an attempt to do something that exceeds a maximum allowable size, such as appending too many characters to a string.-
out_of_range
: used to report that an argument value is not in the expected range, such as when a wrong index is used in an array-like collection or string.This exception is typically thrown in array-based classes by the
at(INDEX)
method:std::vector<int> myvector(10); try { myvector.at(20)=100; // vector::at throws an out-of-range }
It the
operator[](INDEX)
member is used instead, no exception is thrown and the process crashes or the wrong memory is accessed without a warning if the range is exceded.
-
ios_base::failure
: may be thrown when a stream changes its state due to an error or end-of-file.-
runtime_error: used to report errors that can only be detected during runtime. It serves as a base class for several runtime error exceptions:
range_error
overflow_error
underflow_error
bad_exception
: used to handle unexpected exceptions. It does this by using the functionunexpected()
.unexpected()
is called if a function throws an exception that is not listed in an exception specification
Header Files for Exception Classes
The base class exception and class bad_exception
are defined in <exception>
. Class bad_alloc
is defined in <new>
. Classes bad_cast
and bad_typeid
are defined in <typeinfo>
. Class ios_base::failure
is defined in <ios>
. All other classes are defined in <stdexcept>
.
Writing Your Own Exception Classes
To write an exception class, you should:
-
Derive your class from
std::exception
or some other STL exception class, as inclass my_exception_class : public std::exception {...
-
override the
virtual const char* what() const noexcept
method instd::exception
-
Optionally add some features such as initializing with a string or printing detailed information, as explained in the following subsections.
Writing Exception Classes that Initialize with a String
Just add a non-public const string member and a constructor that takes a string:
#include <exception> class string_initializable_exception : public std::exception { const std::string str; public: const char* what() const noexcept override { return str.c_str(); }; string_initializable_exception(const char * txt) : str(txt) {}; };
Note Quite probably you don't want to provide a default constructor, though.
Writing Exception Classes that Build a Formated String through an std::sstream
You want to code an exception whose error message is as rich in information as possible by incorporating input parameters into the string that member what()
outputs.
In all the examples in this subsection, std::stringstream
ss is built with parameters (in the constructor's body), then assigned to member std::string
s, wherefrom a C-string is extracted.
Yes, you might build your exception message outside your exception object and then initialize an string_initializable_exception
object with that string. But let us assume you aim for something more automatic
. You want to build the error message inside your exception object.
Now, since initialization is complex, member string s must not be marked as const
as the message string needs to be built in stages inside the constructor's body, as already explained.
I shall first illustrate a simple class that takes one integer parameter, x:
#include <exception> #include <sstream> class x_exception : public std::exception { std::string s; std::stringstream ss; public: const char* what() const noexcept override { return s.c_str(); }; x_exception() = delete; x_exception(int x) : s("x=") { ss << x; s += ss.str(); }; };
Here, std::stringstream
ss is used for converting a number to a string.
The next example is more sophisticated and allows for more complexity.
Also, two parameters of deducible types X
and Y
are read:
...
...
Order of catch blocks
Catch blocks for exceptions by reference will also latch onto derived exception classes, so it is advisable to place the most specific catch blocks at the start.
Lastly, all exceptions
is selected for as catch (...). Here is an example. Assume that derived_exception
is derived from exception
, and that there are other exceptions not derived from exception
:
#include <exception> try { // exception-throwing code } catch (derived_exception & dex) { // code handling derived_exception } catch (exception & dex) { // code handling exception objects and derived objects } catch (...) { // code handling all remaining exceptions } // code resumes here
Exception specification
A function may be declared to throw only one or several classes of exceptions:
voide f() throw (excpt1, excpt2);
Then, if f()
throws a different exception, global unexpected()
is called.
An empty specification list means that no exception may be thrown.
Exception specifications help other developers understand how your code works and behaves.