EE4E. C++ Programming Lecture 6 Advanced Topics
Contents Introduction Introduction Exception handling in C++ Exception handling in C++ An object oriented approach to exception handling try-catch model of exception handling Exception handling example Re-throwing exceptions Exception specifications Multi-threading Multi-threading Thread creation Thread priority Thread synchronisation
Introduction We will look at 2 more advanced topics in C++ We will look at 2 more advanced topics in C++ Exception handling Multi-threading
An object oriented approach to exception handling Robust C++ programs must include exception handling Robust C++ programs must include exception handling Exceptions are error conditions encountered in executing class methods Exceptions are error conditions encountered in executing class methods Attempting to read past an end of file Attempting to read a file that doesn’t exist Trying to open a malformed URL Divide by zero Taking the square root of a negative number etc
Normal error handling (in C) would return an error code (eg. –1) Normal error handling (in C) would return an error code (eg. –1) class myClass { public: int readFile(….) { do { if (!end_of_file) // read the file else return –1; } while not_end_of_file return number_of_bytes_read; } };
This is a simple sometimes effective method but: This is a simple sometimes effective method but: Sometimes not possible to return a valid error code. Not object oriented! No information about the error is contained in the error code Application code gets ‘polluted’ with error checking code It would be nice to have all of the error handling in one place The method might not be able to return normally from the error. An example would be if a resource the method was accessing was not available
The try-catch model of exception handling Object-oriented applications comprise the use of pre-defined components (objects) by the application and the interaction between these objects Object-oriented applications comprise the use of pre-defined components (objects) by the application and the interaction between these objects The object cannot know in advance how the application will process any error conditions it encounters The try-catch mechanism is a means by which errors in object code can be communicated in a consistent way to the calling application
Application Object 1 Method call Error condition try clause catch clause
Exception handling example We will look at a simple example of exception handling involving handling a simple divide by zero error We will look at a simple example of exception handling involving handling a simple divide by zero error The exception handler simply prints out an error message However, the key point is that the program doesn’t terminate but allows the user to continue
#include class DivideByZeroException : public exception { public: DivideByZeroException::DivideByZeroException() : exception("Attempted divide by zero") {} }; double quotient(int num, int den) { if (den==0) throw DivideByZeroException(); return (double)(num)/den; }
int main() { int number1,number2; double result; cout<<"Enter two integers : "; while (cin >> number1 >> number2) { try { result=quotient(number1,number2); cout << "The quotient is " << result << endl; } catch(DivideByZeroException &dzException) { cout << "Exception! " << dzException.what() << endl; } cout << "\nEnter two integers : "; } return 0; }
Key point is the calling of quotient() within the try clause Key point is the calling of quotient() within the try clause quotient() throws the DivideByZeroException exception using the throw() keyword if a zero denominator is input This is then caught in the catch clause immediately after the try clause
Re-throwing exceptions Often, the exception handler is unable to adequately process the exception Often, the exception handler is unable to adequately process the exception For example, it might not be appropriate for the exception to be handled within the object in which the exception was generated Typically, the exception is then re-thrown on to an outer object It is possible to have chains of unhandled exceptions re-thrown At some level, the exception must be handled
main Object 2 Object 1 throws exception Calls method of re-throw handles exception re-throw
class MyClass1 { public: MyClass1() {} void aMethod1() { try { throw exception(); } catch (exception &caughtException) { cout << "Exception thrown in aMethod1" << endl; throw; } };
class MyClass2 { public: MyClass2() {} void aMethod2() { MyClass1 myObject1; try { myObject1.aMethod1(); } catch(exception &caughtException) { cout << "Exception re-thrown in aMethod2" << endl; throw; } };
int main() { MyClass2 myObject2; try { myObject2.aMethod2(); } catch(exception &caughtException) { cout << "Exception handled in main" << endl; } cout << “Program terminates "; return 0; }
In this simple example, an exception in generated in one object, re-thrown in the handler of the calling object and then handled in the main program In this simple example, an exception in generated in one object, re-thrown in the handler of the calling object and then handled in the main program The program produces the following output: Exception thrown in aMethod1 Exception re-thrown in aMethod2 Exception handled in main Program terminates
Exception heirarchy C++ includes a hierarchy of exception classes headed by the base class exception C++ includes a hierarchy of exception classes headed by the base class exception Includes a virtual method what() which derived classes can override to issue appropriate error messages All exception classes defined in header All exception classes defined in header
Exception specifications An exception specification (a throw list) enumerates a list of exceptions that a function can throw An exception specification (a throw list) enumerates a list of exceptions that a function can throw Indicates that the function throws exceptions only of types ExceptionA, ExceptionB, ExceptionC Indicates that the function throws exceptions only of types ExceptionA, ExceptionB, ExceptionC If it throws a different type of exception, then function unexpected() is called which normally aborts the program int aFunction(int arg) throw (ExceptionA, ExceptionB, ExceptionC) { // function body)
(No exception specification.) Indicates function can throw any exception (No exception specification.) Indicates function can throw any exception (Empty specification.) Indicates the function cannot throw an exception (Empty specification.) Indicates the function cannot throw an exception If it does, function unexpected() is called int aFunction(int arg) { // function body) int aFunction(int arg) throw() { // function body)
Comparison with Java Comparison with Java Exception handling almost identical in Java and C++ with a few minor syntactic differences The main difference is a member function must advertise when it throws an exception in Java This function must then be called in a try-catch clause int aFunction(int arg) throws IOException // Java { // function body)
Stack unwinding This is a simple mechanism for handling uncaught exceptions This is a simple mechanism for handling uncaught exceptions The function call stack is unwound to some catch clause in the function calling chain Identical to simply re-throwing the exception
The following example is similar to a previous one – stack unwinding essentially causes the exception to be re-thrown to the calling object The following example is similar to a previous one – stack unwinding essentially causes the exception to be re-thrown to the calling object Only real difference is no explicit try-catch clauses Only real difference is no explicit try-catch clauses This example throws a runtime_error exception Standard C++ base class for representing run time errors
main Object 2 Calls method of stack unwind handles exception Object 1 throws exception
#include using std::runtime_error; class MyClass1 { public: MyClass1() {} void aMethod1() { throw runtime_error("runtime error in aMethod1"); } };
class MyClass2 { public: MyClass2() {} void aMethod2() { MyClass1 myObject1; myObject1.aMethod1(); } };
int main() { MyClass2 myObject2; try { myObject2.aMethod2(); } catch(runtime_error &error) { cout << "Exception occured: " << error.what() << endl; } return 0; }
Processing new failures When the object allocation operator new fails, it is important to handle this error When the object allocation operator new fails, it is important to handle this error Otherwise it can lead to pointer access errors In older compilers, new returns 0 on failure In older compilers, new returns 0 on failure More recent compilers cause a new failure to return a bad_allocation exception More recent compilers cause a new failure to return a bad_allocation exception bad_allocation defined in header file
‘Old-style’ handling of new failures: ‘Old-style’ handling of new failures: int main() { int* ptr = new int[500000]; if (ptr==0) { cout << “Memory allocation failure”; return 0; } else// Successful object allocation { // Use allocated memory } return 1; }
Exception handling approach to new failure Exception handling approach to new failure #include int main() { try { int* ptr = new int[500000]; } catch ( bad_alloc &memoryAllocException) { cout << “Memory allocation failure” << memoryAllocException.what(); } return 0; }
C++ allows even more flexibilty by allowing the programmer to define their own exception handler on new failure C++ allows even more flexibilty by allowing the programmer to define their own exception handler on new failure set_new_handler() registers a function to be called on new failure Allows the possibility of smart memory management Useful for embedded systems where memory issues are critical
#include void myHandler() { cerr << “myHandler called”; abort(); } int main() { set_new_handler(myHandler) int* ptr = new int[500000]; cout << “Memory allocated”; return 0; }