320 likes | 438 Views
Exceptions and Exception Handling. Where are we. We know OO It’s convenient (class construct and inheritance) It’s powerful (polymorphism) Now Exceptions (robust design) Templates (abstract reuse / meta-programming) Computer science techniques. Errors are going to happen.
E N D
Where are we • We know OO • It’s convenient (class construct and inheritance) • It’s powerful (polymorphism) • Now • Exceptions (robust design) • Templates (abstract reuse / meta-programming) • Computer science techniques
Errors are going to happen. • Some errors are predictable and have l • Two ways to handle • Detect and correct • Pass the buck
Plan for the common errors and provide graceful error handling. • The class ensures the file is open regardless of errors. class File { public: // This operation opens a file for reading and writing. // If the file does not exist, one is created. // void open(string &path); }; What if the file cannot be created? What if the path provided is invalid?
An alternative is to provide feedback and let the user decide what to do. bool class File { public: // This operation opens a file for reading and writing. If the file does not // exist, one is created. This operation returns true if the file was // successfully opened and false it not. // bool open(string &path); }; enum Down side: The user has no idea what went wrong. class File { public: enum Status {OK, BAD_PATH, UNKNOWN_ERROR}; // This operation opens a file for reading and writing. If the file does not // exist, one is created. This operation returns OK if the file was // successfully opened, BAD_PATH if the supplied path was not of a valid // format and UNKNOWN_ERROR in all other cases. // Status open(string &path); };
A traditional OS status indicator is errno. • Used by various pieces of the OS to indicate the status of the last call made. #include <stdio.h> #include <errno.h> int main () { FILE * pFile; pFile = fopen ("myfile.txt","r"); if (pFile!=NULL) { fputs ("fopen example",pFile); fclose (pFile); } else { printf("ERRNO: %d\n", errno); } return 0; } See: Microsoft errno values
You are more familiar with fstream which uses error flags within the class. #include <iostream> #include <fstream>> int main() { fstream test; cout << "OPENING THE FILE FOR OUTPUT" << endl; test.open("test.dat", ios::out); if (test.failbit) { cout << "** Failbit set. Can't open the file" << endl; } test.close(); }
Some problems are not so tidy. • Consider the function: • What should happen if den is zero? intgetQuotient(intnum, int den);
Exceptions signal a severe situation outside normal assumptions. • Indicate a truly exceptional situation • Task can’t be performed • No natural course of action to resolve intgetQuotient(intnum, int den); The operation cannot faithfully be performed.
Deciding when to use exceptions and when to use status indicators could be its own course. • We are not going to discuss design decisions here, mostly mechanics • Do not use exceptions lightly as it’s • Expensive • Very disruptive • Requires great care
Exception Basics • Exceptions are thrown by an operation • Any object can be thrown • Exceptions are caught by the caller • We typically plan for a collection of exceptions
You can enable exceptions for file IO. #include <iostream> #include <fstream>> int main() { fstream test; try { cout << "OPENING THE FILE FOR OUTPUT" << endl; test.open("test.dat", ios::out); test.exceptions ( fstream::eofbit | fstream::failbit | fstream::badbit ); } catch (ifstream::failure e) { cout << "** Exception. Can't open the file" << endl; } test.close(); }
Throwing Exceptions • Uses keyword throw void File::open(string &path) { if (currentFile.is_open) { currentFile.close(); } if (!isValidPath(path)) { throw “Supplied path is invalid”; } currentFile.open(path); if (!currentFile.is_open()) { throw “Unable to open file.”; } } An instance of string is “tossed” to the caller.
What happens when an exception is thrown? • Execution of the method stops where the exception is thrown • Method is exited • All local objects are destroyed
void File::open(string &path) { if (currentFile.is_open) { currentFile.close(); } if (!isValidPath(path)) { throw “Supplied path is invalid”; } currentFile.open(path); if (!currentFile.is_open()) { throw “Unable to open file.”; } } If this exception is thrown… … this code is passed over.
To catch exceptions use a try-catch block. File *f = new File(); try { f->open(“someFile.txt”); } catch (string s) { cout << “Error: “ << s << endl; // Other error handling. } // When we arrive here, we assume we are in a // good position to move forward. If not, we // should have exited this block somehow. If this this throws a string object… … it is caught by this block … … and this code is executed.
What if something other than a string object is thrown? File *f = new File(); try { f->open(“someFile.txt”); } catch (string s) { cout << “Error: “ << s << endl; // Other error handling. } // When we arrive here, we assume we are in a // good position to move forward. If not, we // should have exited this block somehow. If this this throws a int object… … this block does not catch the error … … and the exception is propagated.
Propagation passes it up to the next level. • What happens if an exception occurs in operation invocation z() and no exception handling takes place? f() g() z() Unhandled in F, propagates Unhandled in G, propagates Unhandled in *, propagates throwsexception!!
What happens if the things get back to main()? main() g() z() Unhandled in main(), system crashes Unhandled in G, propagates Unhandled in *, propagates throwsexception!!
You can plan for unexpected exceptions. • The default handler deals with all objects not caught by other catch blocks. File *f = new File(); try { f->open(“someFile.txt”); } catch (...) { cout << “Error: Something bad happened.“ << endl; // Other error handling. } This block catches anything that’s thrown.
Good practice: • Indicate what exceptions are thrown and why • If none, state that explicitly class File { public: // This operation opens a file for reading and writing. An exception // of type string containing the error message is thrown if any // errors occur. No other types of exceptions are thrown. // void open(string &path); // This operation closes the file. There are no exceptions thrown. // void close(); };
Exception Classes • Exception classes can be user-defined • Provides for meaningful exceptions class DivisionByZero {}; class Fraction { private: int num, den; public: Fraction() {num = 0; den=1;} Fraction(int value) {num = value; den=0;} // This operation closes the file. Attempt to divide by // zero results in DivisionByZero to be thrown. In such // a case, the value remains unchanged. // void operator/=(Fraction &rhs) { if (rhs.num = 0) { throw DivisionByZero(); } num *= rhs.den; den *= rhs.nunm; }; Fraction f; Fraction g(7); try { g/=f; } catch (DivisionByZero d) { cout << “Division by zero” << endl; } Of course, the issue now is, how do we progress?
Exception Classes • Can define exceptions that carry info class DivisionByZero { private: string className; string operationName; string timeStamp; static string readClock() { . . . } public: DivisionByZero(string initClassName, string initOperationName) { className = initClassName; operationName = initOperationName; timeStamp = readClock(); } string getMessage() { return timeStamp + “: “ + className + “.” + operationName + “ detected division by zero.”; } };
Exception Class Hierarchies • It is useful to combine polymorphism with exceptions Exception UnexpectedFormatException IOException DivisionByZeroException
Refined Exception Handling class Exception { private: string className; string operationName; string timeStamp; static string readClock() { . . . } protected: string getHeader() { return timeStamp + “: “ + className + “.” + operationName public: Exception(string initClassName, string initOperationName) { className = initClassName; operationName = initOperationName; timeStamp = readClock(); } virtual string getMessage()=0; }; • Using inheritance class DivisionByZeroException : public Exception { public: DivisionByZeroException(string initClassName, string initOperationName) : Exception(initClassName, initOperationName) {} virtual string getMessage() { return getHeader() + “ detected division by zero.”; }; };
Refined Exception Handling class Fraction { private: int num, den; public: Fraction() {num = 0; den=1;} Fraction(int value) {num = value; den=1;} // This operation closes the file. Attempt to divide by // zero results in DivisionByZero to be thrown. In such // a case, the value remains unchanged. // void operation/=(Fraction &rhs) { if (rhs.num = 0) { thrownew DivisionByZero(string(“Fraction”), string(“/=“)); } num *= rhs.den; den *= rhs.nunm; };
Refined Exception Handling • Compare the two File *f = new File(); try { f->open(“someFile.txt”); } catch (...) { cout << “Error: Something bad happened.“ << endl; // Other error handling. } File *f = new File(); try { f->open(“someFile.txt”); } catch (Exception *e) { cout << e->getMessage() << endl; // Other error handling. }
Cleaning Up • In our examples, exceptions are being thrown before any work is done • This is not always the case • If the operation starts to modify the state, you might need to roll it back to ensure coherency
Cleaning Up class Fraction { private: int num, den; public: Fraction() {num = 0; den=1;} Fraction(int value) {num = value; den=1;} // Attempt to divide by zero results in DivisionByZero // to be thrown. In such a case, the value remains // unchanged. // void operation/=(Fraction &rhs) { int lnum = num * rhs.den; int lden = den * rhs.nunm; if (rhs.num = 0) { thrownew DivisionByZero(string(“Fraction”), string(“/=“)); } num = lnum; den = lden; }; class Database { private: Data d; File f; public: // This operation updates the local data and saves // it to the database. // void update(Data newData) { oldData = d; d.merge(newData); try { d.writeToFile(f); } catch (IOException e) { d = oldData; } } };
Ponder this… • Can we throw exceptions in a constructor? • Can we throw exceptions in a destructor? • If so, what happens?
Constructor Exceptions • Yep • There is a definite need • How can a Fraction be created with a 0 denominator? • If a constructor fails, the object is not created class Fraction { private: int num, den; public: Fraction() {num = 0; den=1;} Fraction(int value) {num = value; den=1;} Fraction(int initNum, int initDen) { if (initDen == 0) { throw new DivisionByZeroException( sting(“Fraction”), string(“Fraction(int, int )); } num = initNum; den = initDen; } void operation/=(Fraction &rhs); };
Destructor Exceptions • Yep • Basic approach: don’t do it • Does it really make sense?