Error Handling
\(ax^2 + bx + c = 0\)We can solve the equation for its roots with this formula:
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 references 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;
double 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?
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
Exception specifications have been deprecated in C++11 so you won't use them. They have been replaced with the noexcept specifier, which we won't cover here. I've just left these notes here for historical reasons.
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 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 {
f2();
}
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.
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 { 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.
List::Node *List::new_node(int data) const { // Make sure we have room 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; }