Exception Handling
Definitions
- an exception is raised by throwing an expression
- The type of the thrown expression, together with the current call chain, determines which handler will deal with the exception.
- The selected handler is the one nearest in the call chain that matches the type of the thrown object.
- The type and contents of that object allow the throwing part of the program to inform the handling part about what went wrong.
- The selected handler is the one nearest in the call chain that matches the type of the thrown object.
- exception safe code: code that properly “cleans up” during exception handling
- must ensure that
- objects are valid,
- resources don’t leak,
- the program is restored to an appropriate state
- must ensure that
throw Expression
- throw expressions, which the detecting part uses to indicate that it encountered something it can’t handle.
- We say that a
throwraises an exception.
- We say that a
Why not use Return Codes?
Naive way: by returning an error indicator:
Sales_item item1, item2;
cin >> item1 >> item2;
// first check that item1 and item2 represent the same book
if (item1.isbn() == item2.isbn()) {
cout << item1 + item2 << endl;
return 0; // indicate success
} else {
cerr << "Data must refer to same ISBN"
<< endl;
return -1; // indicate failure
}
Better: separate the part that adds the objects from the part that manages the interaction with a user:
- throw an expression (that is an object of type
runtime_error) - throwing an exception
- terminates the current function (calls
std::terminatewhich itself callsstd::abort) and- see std::terminate
- transfers control to a handler that will know how to handle this error
- terminates the current function (calls
- Because the statements following a
throware not executed, athrowis like areturn- It is usually part of a conditional statement or is the last (or only) statement in a function.
- type
runtime_error- in the
stdexceptheader - must initialize a
runtime_errorby giving it astringor a C-style character string
- in the
// first check that the data are for the same item
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
// if we're still here, the ISBNs are the same
cout << item1 + item2 << endl;
try Block
- try blocks, which the handling part uses to deal with an exception.
- A try block starts with the keyword
tryand ends with one or morecatchclauses. - Exceptions thrown from code executed inside a
tryblock are usually handled by one of thecatchclauses. - Because they “handle” the exception, catch clauses are also known as exception handlers.
- A try block starts with the keyword
- Following the
tryblock is a list of one or morecatchclauses. - A
catchconsists of three parts:- the keyword
catch, - the declaration of a (possibly unnamed) object within parentheses (referred to as an exception declaration),
- and a block.
- When a
catchis selected to handle an exception, the associated block is executed.
- When a
- the keyword
- Once the
catchfinishes, execution continues with the statement immediately following the lastcatchclause of thetryblock - When a handler is entered, objects created along the call chain will have been destroyed. (see Stack Unwinding)
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} // ...
Example
// https://www.w3schools.com/cpp/cpp_exceptions.asp
try {
int age = 15;
if (age >= 18) {
cout << "Access granted - you are old enough.";
} else {
throw (age);
}
}
catch (int myNum) {
cout << "Access denied - You must be at least 18 years old.\n";
cout << "Age is: " << myNum;
}
Standard Exceptions
Usage:
- to report problems encountered in the functions in the standard library
- also intended to be used in the programs we write
Headers:
- 4 headers
exception- defines
exception(exception without additional information)
- defines
stdexcept- defines general-purpose exception classes:
exceptionruntime_errorrange_erroroverflow_errorunderflow_errorlogic_errordomain_errorinvalid_argumentlength_errorout_of_range
- defines general-purpose exception classes:
new- defines
bad_alloc
- defines
type_info- defines
bad_cast
- defines
Exception Classes
- are used to pass information about what happened between a
throwand an associatedcatch. - we can create, copy, and assign objects of any of the exception types
- initialization
- most exception type objects are initialized from either a
stringor a C-style string (that provides additional information about the error that occurred) - types that can only be default initialized (cannot take an initializer)
exceptionbad_allocbad_cast
- most exception type objects are initialized from either a
- exception types define a
whatfunction- takes no arguments
- returns a
const char*that points to a C-style character string- contents of returned string:
- types that take a string initializer: return that string
- other types: return value depends on the compiler
- contents of returned string:
- purpose: provide some sort of textual description of the exception thrown
// https://cplusplus.com/reference/exception/exception/what/
// exception::what
#include <iostream> // std::cout
#include <exception> // std::exception
struct ooops : std::exception {
const char* what() const noexcept {return "Ooops!\n";}
};
int main () {
try {
throw ooops();
} catch (std::exception& ex) {
std::cout << ex.what();
}
return 0;
}
Rethrow
- a
throwthat is not followed by an expression (“empty”throw) - to pass an exception out to another
catch- the (current) exception object is passed up the chain
- can appear only in
- in a
catch - in a function called (directly or indirectly) from a
catch
- in a
- if it appears in other places:
std::terminateis called
catch (my_error &eObj) { // specifier must be a reference type (if we want to modify the exception object, before rethrowing)
eObj.status = errCodes::severeErr; // modifies the exception object
throw; // the status member of the exception object is severeErr
} catch (other_error eObj) {
// ...
throw;
}
Catch-All Handler
- use:
- “The series of handlers is rather like a
switchstatement. The handler marked(...)is rather like adefault(in aswitchstatement). Note, however, that there is no fall through from one handler to another as there is from onecaseto another.”, Stroustrup paper - “This can be used as a default handler that catches all exceptions not caught by other handlers”, cplusplus
- “The series of handlers is rather like a
- to catch any exception that might occur, regardless of type
- use an ellipsis for the exception declaration
- A catch-all clause matches any type of exception
- often used in combination with a rethrow expression
- again, to pass the exception out to another
catch
- again, to pass the exception out to another
- If a
catch(...)is used in combination with other catch clauses, it must be last.- Any
catchthat follows a catch-all can never be matched.
- Any
void manip() {
try {
// actions that cause an exception to be thrown
}
catch (...) {
// work to partially handle the exception
// (eg. "delete" some object to free memory)
throw; // rethrow (pass the exception to some
// other handler)
}
}
related: stackoverflow
Stack Unwinding
- read: Lip, p773 “Stack Unwinding” !!!
- “the search for a matching
catchclause” - the process
- that starts when an exception is thrown
- that continues up the chain of nested function calls until
- a
catchclause for the exception is found, or- effect: executing the code inside that
catch. When thecatchcompletes, execution continues at the point immediately after the lastcatchclause
- effect: executing the code inside that
- the
mainfunction itself is exited without having found a matchingcatch- effect: the program calls the library
terminatefunction
- effect: the program calls the library
- a
- An exception that is not caught terminates the program.
- definition: cppreference
Matching
- During the search for a matching
catch, thecatchthat is found is not necessarily the one that matches the exception best.- Instead, the selected
catchis the first one that matches the exception at all.
- Instead, the selected
- As a consequence, in a list of
catchclauses, the most specializedcatchmust appear first.
Object Destruction when Blocks are Exited
- read: Lip, p773 “Stack Unwinding” !!!
- from Lippman:
- Ordinarily, local objects are destroyed when the block in which they are created is exited. Stack unwinding is no exception.
- When a block is exited during stack unwinding, the compiler guarantees that objects created in that block are properly destroyed.
- If a local object is of
- class type, the destructor for that object is called automatically.
- built-in type, the compiler does no work to destroy that object.
- this includes raw pointers:
int *a,MyClass *b(note:bis a raw pointer, althoughMyClassis a class type) - good example: “On you specific questions, when an exception is thrown in a constructor, all fully constructed subobjects will be destroyed. That means that in the case of
bit will be destroyed, in the case ofc, it being a raw pointer, nothing is done.”
- this includes raw pointers:
- from cppreference:
- “Once the exception object is constructed, the control flow works backwards (up the call stack) until it reaches the start of a
tryblock, …” - “As the control flow moves up the call stack, destructors are invoked for all objects with automatic storage duration that are constructed, but not yet destroyed, since the corresponding try-block was entered, in reverse order of completion of their constructors.”
- “If an exception is thrown from a constructor or (rare) from a destructor of an object (regardless of the object’s storage duration), destructors are called for all fully-constructed non-static non-variant members and base classes, in reverse order of completion of their constructors.”
- “Once the exception object is constructed, the control flow works backwards (up the call stack) until it reaches the start of a
Uncaught Exceptions
(to understand destructor1.cpp)
Question: Why do we get ./a.out 1 : A()0 A()1 terminate called after throwing?
Answer: Because the destructors (here: ~A()0) are not called until the catch is found.
cppreference:
“If an exception is thrown and not caught, including exceptions that escape (…) the main function (…), then std::terminate is called. It is implementation-defined whether any stack unwinding takes place for uncaught exceptions.”
From stackoverflow:
You seem to be under the impression that the destructors are called while searching for a matching catch(). That does not have to be the case.
Destructors from the current location in the program to the matching catch() end up getting called, but that does not need to start happening until the catch is found.
The second block just states that if no catch is found, then the program is allowed to immediately terminate as soon as it makes that determination.
Handle a Constructor that fails
From isocpp:
Options:
- 1) Throw an exception.
- Constructors don’t have a return type, so it’s not possible to use return codes.
- The best way to signal constructor failure is therefore to throw an exception.
- 2) “zombie” state: If you don’t have the option of using exceptions, the “least bad” work-around is to put the object into a “zombie” state by setting an internal status bit so the object acts sort of like it’s dead even though it is technically still alive.
- In practice the “zombie” thing gets pretty ugly.
related: “Constructors throwing Exceptions” → Notes on “Classes”
Assert
static_assert
From stackoverflow:
Static assert is used to make assertions at compile time. When the static assertion fails, the program simply doesn’t compile. This is useful in different situations, like, for example, if you implement some functionality by code that critically depends on unsigned int object having exactly 32 bits. You can put a static assert like this
static_assert(sizeof(unsigned int) * CHAR_BIT == 32);
in your code. On another platform, with differently sized unsigned int type the compilation will fail, thus drawing attention of the developer to the problematic portion of the code and advising them to re-implement or re-inspect it.