. Plab – Tirgul 10 Exceptions
Error handling in C Up to now we handled our errors in the “C way”: assert return codes global error variable ( errno and perror ) “The problem with C’s approach to error handling could be thought of as coupling—the user of a function must tie the error-handling code so closely to that function that it becomes too ungainly and awkward to use ”
Exceptions C++ introduces the use of exceptions to handle errors. You are already familiar with exceptions in Java. Unfortunately, in C++, due to the more complex memory model, the exceptions are far less simple .
Exceptions – why? u Exceptions are used in C++ for: Handling errors in constructors. Handling memory errors. Math errors.
Throwing exception example If pop() from empty ChangesCollection is illegal we would like to throw an exception. const Change* ChangesCollection::pop() { if (isEmpty()) throw 0;... } In C++ anything can be thrown: basic types (int, double, etc.), pointers, objects, references.
Throwing exception const Change* ChangesCollection::pop() { if (isEmty()) throw EmptyCollectionE(__FILE__, __LINE__);... } It’s a good practice to define special exception classes (usually there will be a hierarchy of these): If an exception is not caught it will abort the program. Unlike Java you will get no info about the exception (such as where it was thrown, the call stack).
Catching an exception We use try and catch similarly to Java. Consider: try {... } catch (CollectionE& e) { e.printErrorMessage(); throw; // re-throw the exception } Note that catching by reference enables polymorphism.
Catching an exception cntd. We can catch several different exceptions at a time: try {... } catch (EmptyCollectionE& e) {... } catch (CollectionE& e) {... } catch (...) { // catch ANY exception... } Note the order of the catch clauses
Exception specification u Unlike Java, functions (methods) do not have to declare what exceptions they can throw: int f(); // can throw any exception u Still, it’s a good style to declare the exceptions your functions may throw: int f() throw (BadArg, int*); // f can throw only BadArg, int* u This function does not throw exceptions: void g() throw();
Stack unwinding u When exception is thrown all automatic variable of the function on the stack are freed. u What about other resources, like: open files memory on heap FILE* f = fopen(filename, “r”); try {... } catch(...) { fclose(f); throw; } fclose(f); Note that in most C++ compilers there is no finally.
Stack unwinding cntd. u This solution is tedious if the resources are allocated in many different places. u A better solution is to wrap the resource in an object, which will be automatically deallocated:
Stack unwinding cntd. FilePtr f(filename, “r”); try {... // here f is used } catch(...) {... } The file is closed by FilePtr ’s destructor, thus it will be closed regardless of the way we exit (normally or with exception).
Exceptions in constructor u Very convenient u If the object is constructed in (dynamic) heap and its constructor throws exception, the memory will be released automatically. u The destructor won’t be called. u Example: Circle’s constructor throws exception when provided with negative radius: try { c = new Circle(1, 2, -1); } catch(...) { // we should not delete c ! }
Exceptions in constructor u Example: Circle’s constructor throws exception when provided with negative radius: try { c = new Circle(1, 2, -1); } catch(...) { // we should not delete c ! }
Standard exceptions exception logic_errorruntime_error ios::failure invalid_argument out_of_range bad_alloc And others.
Read yourselves: auto_ptr Similarly to the way we wrapped a FILE pointer, we can wrap any pointer, so that it is automatically deleted when we leave the scope. The auto_ptr which is part of the standard library is a template class for this purpose. MyClass* f(...) { auto_ptr p(new MyClass());... return p.release(); } If an exception occurs prior to return, the instance of MyClass will be automatically deleted by p ’s destructor. u After auto_ptr is copied (to another one) it points nowhere.