Error Handling
\(ax^2 + bx + c = 0\)
We can solve the equation for its roots with this formula:
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
Day One
For simplicity, we will only calculate one root (the "+" portion of the equation).
The QRoot function (for Quadratic Root) looks like:
We would use it something like this:double QRoot(double a, double b, double c) { double determinant = (b * b) - (4 * a * c); return (-b + std::sqrt(determinant)) / (2 * a); }
#include <iostream>
#include <cmath>
double QRoot(double a, double b, double c)
{
double determinant = (b * b) - (4 * a * c);
return (-b + std::sqrt(determinant)) / (2 * a);
}
int main()
{
// -0.438447
std::cout << "QRoot a=1, b=5, c=2: " << QRoot(1, 5, 2) << std::endl;
// Error, taking square root of negative number (-nan)
std::cout << "QRoot a=1, b=2, c=5: " << QRoot(1, 2, 5) << std::endl;
// Error, divide by 0 (-nan)
std::cout << "QRoot a=0, b=2, c=5: " << QRoot(0, 2, 5) << std::endl;
return 0;
}
The output
What are the pros and cons of this approach?QRoot a=1, b=5, c=2: -0.438447 QRoot a=1, b=2, c=5: -nan QRoot a=0, b=2, c=5: -nan
Day Two
double QRoot(double a, double b, double c)
{
double determinant = (b * b) - (4 * a * c);
if (determinant < 0) // protected against: std::sqrt(-x)
{
std::cout << "Can't take square root of a negative number." << std::endl;
std::abort();
}
else if (a == 0) // protected against: x / 0
{
std::cout << "Division by 0." << std::endl;
std::abort();
}
return (-b + std::sqrt(determinant)) / (2 * a);
}
Note: The client code is unchanged. Output from the abort() function (depends on the
version of the compiler.)
GNU (on Cygwin):
Microsoft:Can't take square root of a negative number. 160 [sig] a 1716 open_stackdumpfile: Dumping stack trace to a.exe.stackdump 160 [sig] a 1716 open_stackdumpfile: Dumping stack trace to a.exe.stackdump 1572257 [sig] a 1716 E:\Data\Courses\Notes\CS170\Code\Exceptions\a.exe: *** fatal error - E:\Data\Courses\Notes\CS170\Code\Exceptions\a.exe: *** called with threadlist_ix -1
Borland:Can't take square root of a negative number. This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.
Can't take square root of a negative number. Abnormal program termination
What are the pros and cons of this approach?
Day Three
bool QRoot(double a, double b, double c, double *result) // Could use a reference as well
{
double determinant = (b * b) - (4 * a * c);
// protected against: std::sqrt(-x), x / 0
if ( (determinant < 0) || (a == 0) )
{
*result = 0.0; // return something safe
return false; // indicates there was a problem
}
*result = (-b + std::sqrt(determinant)) / (2 * a);
return true; // indicates all is well
}
int main()
{
double answer;
bool success = QRoot(1, 5, 2, &answer);
if (success)
std::cout << "QRoot a=1, b=5, c=2: " << answer << std::endl;
else
std::cout << "QRoot failed for some reason" << std::endl;
return 0;
}
What are the pros and cons of this approach?
This is the biggest problem. We can't use the function call in an expression:
We have to do something tedious like this:double result = QRoot(1, 5, 2) + QRoot(5, 2, 1) + QRoot(3, 2, 1);
Of course, neither of them are actually dealing with the situation where it fails.double result = 0; if (QRoot(1, 5, 2, &answer)) { result += answer; if (QRoot(5, 2, 1, &answer)) { result += answer; if (QRoot(3, 1, 2, &answer)) result += answer; } }
Day Four (read all about exceptions)
A format of the try...catch mechanism:These examples use simple built-in data types for the exception types. You will likely never do that. This is to keep the examples very simple while focusing on the "exceptional" part of the discussion. Proper use of exception classes will be shown near the end.
You can catch multiple exceptions thrown from a try block:int main() { . . . try { // code that might cause an exception (throw) and needs // to be protected } catch (???) // which type of exception to catch? { // code that will handle the exception (catch) from // the try block above } . . . }
Notes:int main() { . . . try { // code that might cause an exception (throw) and needs // to be protected } catch (const char *p) // catch a const char pointer { // code that will handle the char pointer exception from // the try block above } catch (int i) // catch an integer { // code that will handle the integer exception from // the try block above } catch (std::exception e) // catch an "exception" object { // code that will handle the "exception" object from // the try block above } . . . }
Day Five (Use exceptions)
double QRoot(double a, double b, double c)
{
double determinant = (b * b) - (4 * a * c);
// protected against std::sqrt(-x) and division by 0
if (determinant < 0)
throw("Can't take square root of a negative number.");
else if (a == 0)
throw("Division by 0.");
// We only reach this point if no exception was thrown
return (-b + std::sqrt(determinant)) / (2 * a);
}
int main()
{
try // protect code
{
std::cout << "QRoot a=0, b=5, c=2: " << QRoot(0, 5, 2) << std::endl;
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
return 0;
}
Output:
Division by 0.
Day Six
All STL functions indicate which kinds of exceptions, if any, are thrown. Here are some examples:
Note:
at Returns a reference to the element at specified location pos, with bounds checking. insert Inserts elements at the specified location in the container. reserve Increase the capacity of the vector (the total number of elements that the vector can hold without requiring reallocation). clear Erases all elements from the container. After this call, size() returns zero.
(Dynamic) exception specifications have been deprecated in C++11 so you won't use them. They have been replaced with the noexcept specifier, which is covered later. It's still worthwhile to see why they were invented and why they've changed.
// This function promises to only throw a const char * or a double, nothing else
double QRoot(double a, double b, double c) throw(const char *, double)
{
double determinant = (b * b) - (4 * a * c);
// protected against std::sqrt(-x) and division by 0
if (determinant < 0)
throw(determinant); // throw double
else if (a == 0)
throw("Division by 0."); // throw const char *
// We only reach this point if no exception was thrown
return (-b + std::sqrt(determinant)) / (2 * a);
}
int main()
{
try // protect code
{
std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
catch (double value) // catch a double exception
{
std::cout << value << std::endl;
}
return 0;
}
Output:
-8
To indicate that a function doesn't throw any exceptions, use empty parentheses:
What does the following output?double SomeFun(double a, double b, double c) throw() { ... }
int main()
{
// protect code
try
{
// Determinant will be negative
std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
catch (double value) // catch a double exception
{
std::cout << value << std::endl;
}
// protect code
try
{
// a is 0 (divide by 0)
std::cout << "QRoot a=0, b=2, c=1: " << QRoot(0, 2, 1) << std::endl;
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
catch (double value) // catch a double exception
{
std::cout << value << std::endl;
}
return 0;
}
What does the following output?
int main()
{
// protect code
try {
std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
std::cout << "QRoot a=0, b=2, c=1: " << QRoot(0, 2, 1) << std::endl;
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
catch (double value) // catch a double exception
{
std::cout << value << std::endl;
}
return 0;
}
Day Six and a half
Previous versions of C++ have had exception specifications. This was a mechanism that allowed the programmer to "tag" functions with the types of exceptions that might be thrown. For example, this lookup function can potentially throw an exception if the index is out of range:The code at the end:int lookup(const vector<int>& v, int index) throw(std::out_of_range) { return v.at(index); // This method does range checking }
is the exception specification and informs the programmer what may be thrown from the function. It also tells the compiler to prevent any other exceptions from being thrown. If any exception, other than std::out_of_range is thrown, the entire program is terminated.throw (std::out_of_range)
This is sample code that calls the function above:
Output:vector<int> v; v.push_back(10); v.push_back(20); v.push_back(30); try { cout << lookup(v, 2) << endl; // Prints 30 cout << lookup(v, 5) << endl; // Throws a std::out_of_range exception } catch (const std::out_of_range& ex) { cout << ex.what() << endl; // Display details about the exception }
30 vector::_M_range_check: __n (which is 5) >= this->size() (which is 3)
There are 3 varieties of exception specifications:
// foo1 will throw no exceptions int foo1() throw(); // foo2 will only throw std::out_of_range or std:bad_alloc int foo2() throw (std::out_of_range, std::bad_alloc); // foo3 may thrown any exception int foo3();
When declaring a function to be noexcept, these both mean the same thing:// Function will not throw an exception void foobar1() noexcept(true) { // do something guaranteed to be safe } // Function may throw an exception void foobar2() noexcept(false) { // do something possibly unsafe } // foobar3 may throw an exception only if foobar2 may throw an exception void foobar3() noexcept(noexcept(foobar2())) { foobar2(); // call possibly unsafe function } // foobar4 will not throw an exception (protects unsafe call to foobar2) void foobar4() noexcept(true) { try { foobar2(); // call possibly unsafe function } catch (/* whatever foobar2 throws */) { // do something with the exception } // safely continue excecuting... }
On a side note, it's a little unfortunate that the terminology kind of reverses the definition of on/true/enabled with off/false/disabled. By setting noexcept to true, you are disabling the ability to throw exceptions. It has always seemed strange to me when you "turn something off" by answering in the affirmative:void foobar() noexcept(true) // explicit void foobar() noexcept // implicit
Q: "Do you want me to not turn the lights off?"Most English speakers will ask in the affirmative:
A: Yes, please do not turn them off.
Q: "Do you want me to turn the lights off?"This would mean we would have except(true) to allow exceptions and except(false) to not allow them. Anyhoo...
A: No, please do not turn them off.
Notes:
Day Seven
Unwinding the Stack after an Exception
Assuming the same code for the QRoot function, what will this code do
void f1()
{
std::cout << "Starting f1..." << std::endl;
QRoot(...); // program flow depends on this call
std::cout << "Ending f1..." << std::endl;
}
void f2()
{
std::cout << "Starting f2..." << std::endl;
f1();
std::cout << "Ending f2..." << std::endl;
}
int main()
{
// protect code
try {
// Any code here will always execute...
f2();
// Any code here may or may not execute...
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
catch (double value) // catch a double exception
{
std::cout << value << std::endl;
}
return 0;
}
Assuming this call in f1: (no exception is thrown)
We getQRoot(1, 5, 3);
Assuming this call in f1: (division by zero)Starting f2... Starting f1... Ending f1... Ending f2...
We getQRoot(0, 5, 3);
Starting f2... Starting f1... Division by 0.
void f1()
{
try
{
QRoot(0, 5, 3); // division by 0
}
catch (const char *s)
{
throw("Error! Please call 1-800-DIV-ZERO for help");
}
}
int main()
{
// protect code
try
{
f1();
}
catch (const char *message) // catch a const char pointer exception
{
std::cout << message << std::endl;
}
return 0;
}
Output:
Error! Please call 1-800-DIV-ZERO for help
Remember this rule:
NEVER catch an exception that you do not intend to do anything about. If you don't know what to do with the exception that you catch, DO NOT CATCH IT! It is meant for some other part of the program to handle. If you have code that catches an exception and just re-throws the exception just caught, you are guilty of ignoring this rule.
Day Eight (Exception Classes)
class SomeClass
{
// code here
};
void f1()
{
throw SomeClass(); // construct and throw SomeClass object
}
int main()
{
try
{
f1();
}
catch (const SomeClass &s)
{
std::cout << "Caught an exception of type SomeClass" << std::endl;
}
return 0;
}
Modifying our String Class
Recall the String class we developed:Our original "error handling":class String { private: char *string_; // the "real" string public: // Constructors, destructor, etc... // overloaded [] operators for subscripting char & operator[](int index); const char & operator[](int index) const; };
Adding an Exception Classchar & String::operator[](int index) { int len = strlen(string_); // Get length of internal string if (index < 0 || index >= len) // Make sure the index is valid { std::cerr << "Bad index" << std::endl; // If bad, print message abort(); // terminate program } else return string_[index]; // Return the char at index }
class SubscriptError
{
public:
SubscriptError(int Subscript) : subscript_(Subscript) {};
int GetSubscript() const { return subscript_; }
private:
int subscript_;
};
Implementation:
char& String::operator[](int index)
{
int len = strlen(string_); // Get length of internal string
if (index < 0 || index >= len) // Make sure the index is valid
throw SubscriptError(index); // Throw exception if invalid
return string_[index]; // Return the char at index
}
We would code the client like this:
int main()
{
String s("Hello"); // Create string "Hello"
try
{
std::cout << s[0] << std::endl; // Get the first character and print it
s[9] = 'C'; // Attempt to change tenth character
std::cout << s << std::endl;
}
catch (const SubscriptError &se)
{
std::cout << "Bad subscript: " << se.GetSubscript() << std::endl;
}
return 0;
}
Output:
H
Bad subscript: 9
Handling Memory Allocation Failure
Now that we know how to deal with out-of-memory errors in C++, we can deal with them.
Modified example from cppreference.com:List::Node *List::new_node(int data) const { Node *node; try { node = new Node(data); // create the node } catch (const std::bad_alloc& ex) { std::cout << "New failed" << std::endl; std::cout << ex.what() << std::endl; std::abort(); // Maybe do something better? } return node; }
#include <iostream> // cout, endl
#include <new> // set_new_handler
//#include <unistd.h> // sleep
static int allocs = 0;
void handler()
{
//sleep(2);
std::cout << "Memory allocation failed, terminating. ["
<< allocs << "]" << std::endl;
// Reset new handler (will throw std::bad_alloc)
std::set_new_handler(0);
}
int main()
{
// Install new handler (no exception will be thrown)
std::set_new_handler(handler);
try
{
// Consume all memory
while (true)
{
std::cout << "Allocating... " << ++allocs << std::endl;
new double[100000000]; // 800,000,000 bytes
}
}
catch (const std::bad_alloc& e)
{
std::cout << "Allocation failed: " << e.what() << '\n';
}
return 0;
}
Example showing recovery from out-of-memory errors:
#include <iostream> // cout, endl
#include <new> // set_new_handler
//#include <unistd.h> // sleep
static int allocs = 0;
static double* memory[200000]; // Magic number! Make sure you don't allocate more than this.
static int errors = 1;
void handler()
{
std::cout << "Memory allocation failed, terminating. ["
<< allocs << "]" << std::endl;
std::cout << "Out of memory: " << errors << std::endl;
//sleep(1);
// Release all of the memory
for (int i = 0; i < allocs; i++)
delete [] memory[i];
allocs = 0;
errors++;
// Reset new handler (will throw std::bad_alloc)
//std::set_new_handler(0);
}
int main()
{
// Install new handler (no exception will be thrown)
std::set_new_handler(handler);
try
{
// Consume all memory
while (true)
{
std::cout << "Allocating... " << allocs << std::endl;
memory[allocs++] = new double[100000000]; // 800,000,000 bytes
}
}
catch (const std::bad_alloc& e)
{
std::cout << "Allocation failed: " << e.what() << '\n';
}
return 0;
}
Note: There is actually a potential bug in the code above.
Day Nine (Derived exception Classes)
It is common to derive all exception objects from the exception class:
class exception
{
public:
exception() throw();
exception(const exception& rhs) throw();
exception& operator=(const exception& rhs) throw();
virtual ~exception() throw();
virtual const char *what() const throw();
};
Deriving SubscriptError from exception:
class SubscriptError : public std::exception
{
private:
int subscript_;
public:
SubscriptError(int Subscript) : subscript_(Subscript) {};
int GetSubscript() const { return subscript_; }
virtual const char *what() const throw();
{
static char buff[80];
sprintf(buff, "Subscript error at index: %d", subscript_);
return buff;
}
};
Now, the client should call the what() method instead:
int main()
{
String s("Hello"); // Create string "Hello"
try
{
std::cout << s[0] << std::endl; // Get the first character and print it
s[9] = 'C'; // Attempt to change ninth character
std::cout << s << std::endl;
}
catch (const SubscriptError &se)
{
std::cout << se.what() << std::endl;
}
return 0;
}
Output:
H
Subscript error at index: 9
Suppose you wanted to be more like the error emitted from
STL's vector class
and provide the length
of the String in the error message:
class SubscriptError : public std::exception
{
private:
int subscript_;
int length_;
public:
SubscriptError(int Subscript, int len) : subscript_(Subscript), length(len) {};
int GetSubscript() const { return subscript_; }
virtual const char *what() const throw();
{
static char buff[80];
sprintf(buff, "Subscript error at index: %i, length is %i", subscript_, length_);
return buff;
}
};
char& String::operator[](int index)
{
int len = strlen(string_); // Get length of internal string
if (index < 0 || index >= len) // Make sure the index is valid
throw SubscriptError(index, len); // Throw exception if invalid
return string_[index]; // Return the char at index
}
Output: H Subscript error at index: 9, length is 5
Note that the dynamic throw specification, throw(), is deprecated in C++11 in favor of exception specifications, noexcept.
Question: What happens if a constructor throws an exception?