3.9. Exception handling¶
An alternative to returning values from functions is to use exceptions.
A C++ exception is a response to an exceptional circumstance that occurs while a program is running, such as an attempt to open a file that does not exist.
Exceptions provide a way to transfer control from one part of a program
(where the error occurs) to another (where the error is ‘handled’).
C++ exception handling is built upon three keywords: try, catch, and throw.
- throw
A program throws an exception when a problem shows up.
- try
A try block identifies a block of code for which particular exceptions will be activated. It’s followed by one or more
catchblocks.- catch
A program handles an exception with one or more catch blocks.
Basic anatomy of an exception
Surround potentially error throwing code in a
tryblockAfter the
tryblock, include 1 or morecatchblocksThe parameter in the catch identifies the type of exception the catch block will handle
try { // execute potentially dangerous statements } // catch a specific class of exceptions catch (const std::exception& e) { std::cout << "Exception occurred.\n"; std::cout << "Details: " << e.what() << std::endl; }
If you specify a try, you must include at least 1 catch.
Passing catch parameters by const reference is considered a best practice
std::exception.what()
Returns a description of what caused the error
More than one
catchblock is acceptableThe special catch
catch (...)
will catch any exception. If you use this, then you should be prepared to really handle anything.
3.9.1. Standard exceptions¶
The standard exceptions in C++ are organized in a class hierarchy.
std::exception is the base class for all exceptions
Classes derived from
std::exceptionbad_alloc: thrown by
newand other memory allocation errorsbad_cast: thrown by
dynamic_castand similarbad_typeid: thrown by
typeidbad_exception: runtime unexpected or pointer exceptions
logic_error: exceptions that should be detected by reading the code
runtime_error: exceptions that theoretically can’t be detected by reading the code
logic_error, andruntime_errorare also exception bases
Classes derived from std::logic_error
domain_error: invalid mathematical domain
invalid_argument: bad parameters or arguments used
length_error: Thrown when a std::string is too large
out_of_range: Used for range checked access, vector.at(x)
Classes derived from std::runtime_error
overflow_error: mathematical overflow
range_error: Thrown when storing an out of range value
underflow_error: mathematical underflow
This list is just a partial set of the exceptions in the standard library.
3.9.2. Using exceptions¶
C++ exceptions are designed to support error handling.
Use throw only to signal an error.
Use catch only to specify error handling actions when
you know you can handle it.
Possibly by translating it to another type and re-throwing an exception of that type.
For example, catching a bad_alloc and re-throwing a no_space_for_file_buffers exception.
Do not use throw to catch a coding error in usage of a function.
Instead, use assert or other mechanism to either stop the program or log the error.
Do not use throw if you discover an unexpected violation of an invariant of your component.
Instead, use assert or other mechanism to terminate the program.
Throwing an exception will not cure memory corruption and may lead to further corruption of important user data.
Use try and catch blocks
if the logic is more clear than checking a condition and returning a value.
For example,
if you need to propagate errors several levels up the stack:
void f1() {
try {
f2();
} catch (const some_exception& e) {
// ... handle error
}
}
void f2() { ...; f3(); ...; }
void f3() { ...; f4(); ...; }
void f4() { ...; f5(); ...; }
void f5()
{
if ( /*...some error condition...*/ )
throw some_exception();
}
Only the code that detects the error, f5(),
and the code that handles the error, f1(), have any clutter.
None of the other functions have to worry about passing error codes either in return values
or in extra parameters that would have to be mutable.
Do not use try blocks to reclaim resources.
This is a Java technique,
which is great for Java, but is not needed in C++.
In C++, use Resource Acquisition Is Initialization (RAII).
Use constructors to allocate resources and use destructors to clean up resources,
Do not use try blocks as a proxy for error return codes.
This results in too many try blocks cluttering up functions,
which harms readability if nothing else.
3.9.3. Exceptions and I/O streams¶
I/O streams can be configured to throw exceptions with std::basic_ios::exceptions.
This object gets and sets the exception mask of the stream.
The exception mask set in the program determines which error states
in the stream will throw an exception if an error is encountered.
If no exception bits are set, then the I/O streams in C++ will not throw any exceptions.
For example:
std::ifstream ifs("in.txt");
ifs.exceptions(std::ifstream::failbit);
At this point, only the failbit will trigger an exception.
I/O Streams may throw ios_base::failure
But since C++11 this class inheritance changed.
ios_base::failure inherits from std::system_error
The end result is that ios_base::failure now has
an error_code member to the exception object it didn’t used to have.
catch (const ios_base::failure& e) {
std::cout << "I/O exception occurred.\n";
std::cout << "Details: " << e.what() << std::endl;
std::cout << "Code: " << e.code() << std::endl;
}
More to Explore
Overview of the error handling library and exceptions
Post from Eric Lippert on vexing exceptions