C++ course
Exception handling
Objectives
- Exceptions - motivation
- Throw
- try..catch
- inheritance
- Standard exceptions
Motivation
In the real world exceptional cases must be handles
double divide(double a, double b)
{
return a/b;
}
int main()
{
divide(10,0); // Will cause division by zero
}
In Procedure languages that would have been done as follows
double divide(double a, double b)
{
if (b == 0) {
cerr << "Division by zero" << endl;
exit(-1);
}
return a/b;
}
Motivation
The procedural way has several problems with it
- The error handling is performed by the callee, which has less information than the caller.
- The function's implementation is contaminated by error handling
- In some case the error handling contaminates both the caller and the callee
- It is not compatible with the object model (see next slide)
class Person
{
string m_name;
bool m_valid;
public:
Person(const string& name):m_name(name), m_valid(true) { if (m_name == "") m_valid = false; }
void doSomething() const {
if (m_valid) {
// Do something..
}
}
};
Exceptions to the rescue
Exceptions can separate code from error handling
class Person
{
string m_name;
public:
Person(const string& name):m_name(name){ if (m_name == "") throw "Person must have a name"; }
void doSomething() const {
// Do something..
}
};
int main()
{
try {
Person p("");
p.doSomething();
} catch(const char* exc) { cout << "Exception caught: " << exc << endl; }
}
There are 3 new keyword to support exception handling
- try - introducing a code block that may throw an exception
- catch - catching an exception of a type if it was thrown
- throw - throw an exception of a type
Throwing exceptions
When an error is encountered, an exception is thrown.
The exception can be of any type
throw "abc"; // const char* is thrown
throw string("abc"); // a string is thrown
throw 1; // an integer is thrown
throw 5.2; // a double are thrown
throw Person("Me"); // A Person object is throw
The function stack will unwind until the exception is caught via a try...catch clause. if there is no appropriate catch clause and the stack is completely unwind the program will call terminate(), terminating the programs execution.
try...catch
A try is declared when we are interested in handling some or all of the potential errors. Each try is followed by a list of catch clauses, each catch can handle a specific type.
try {
// Some error prone functionality
}
catch (const double& d) { /* caught a double */ }
catch (const string& s) { /* caught a string */ }
catch (...) { /* catch ANY exception */ }
class Person
{
/* A Class that may throw an exception
};
void useNamelessPerson()
{
Person p("");
}
int main()
{
try {
useNamelessPerson();
}
catch (const char* esc) { cout << "ERROR: " << esc << endl; }
}
Terminating
As mentioned above the terminate function will be called once the entire stack is unwind and no relevant catch clause was found.
We can provide our own terminate function.
void theTerminate()
{
cout << "Reached my terminate" << endl;
exit(1);
}
int main()
{
set_terminate(theTerminate);
useNamelessPerson();
}
Exception declaration
A function or a method can declare what exceptions they may throw, in case an undeclared exception is thrown from within the function/method during runtime the unexpected() function will be called exiting the program immediately.
void foo() throw (string, int)
{
throw string("String");
}
Just like terminate() we can supply our own unexpected() function via the set_unexpected(...) function.
void theUnexpected()
{
cout << "In my unexpected function" << endl;
}
int main()
{
set_unexpected(theUnexpected);
...
}
Note: This is deprecated in C++11
Re-throwing
Sometimes it makes sense to perform error handling but still propagate the exception (rethrow it)
try {
// Code that can throw an exception
} catch (const string& str) {
// Do some cleanup
throw; // re-throws the exact same exception
}
Exceptions and inheritance
When catching an object that can have several types (Due to inheritance) make sure that the the catch clauses are from the most specific to the more general class
class Base {};
class Derived : public Base {};
int main()
{
try {
foo();
}catch (const Derived& d) { cout << "Caught Derived" << endl; }
catch (const Base& b) { cout << "Caught Base " << endl; }
}
Standard exceptions
Usually we will throw an exception that will contain some relevant information that we can use like the error, line number or even some relevant values. The standard introduced some exceptions.
All exceptions generated by the standard library inherit from std::exception
- logic_error
-
runtime_error
- range_error
- overflow_error
- underflow_error
- regex_error(C++11)
-
system_error(C++11)
- ios_base::failure(since C++11)
- bad_typeid
- bad_cast
- bad_weak_ptr(C++11)
- bad_function_call(C++11)
-
bad_alloc
- bad_array_new_length(C++11)
- bad_exception
- ios_base::failure(until C++11)
The exceptions encode the type of error in the type itself, while additional information will be part of the type and is usually accessible via the what() method.
Standard exceptions - cont
Standard exceptions are used in exactly the same manner as primitive types or user defined exceptions, but, you will have to include stdexcept header file for compilation.
#include <stdexcept>
void foo() {
throw runtime_error("Not implemented yet");
}
int main()
{
try {
foo();
} catch (const runtime_error& e) { cout << "Exception: " << e.what() << endl; }
}
If you need to add some more attributes, you can inherit from any standard exception and add to it.
C++ course
By perplexedpigmy
C++ course
- 1,084