Exceptions As there may be certain actions which can cause run-time problems, we need some sort of mechanism to report and handle these potential errors. We can use the return-value mechanism. This mechanism have several problems: We may forget to check the return value. There may not be an available value we can use. Example!!! We have to check the return value for each and every function separately. The solution: Exceptions!!! The function does not use the return value but rather throws an exception. The calling code will include a special block to handle exceptions. Motivation
Exceptions Exception handling is implemented with 3 keywords: throw … – when an error occurred in a function it will use “throw” to throw a certain object (of any type). try – the “problematic” function call, will be nested within a “try” block. Catch (…) – one or more catch blocks should immediately follow the try block. if the function throws an exception, this block will handle it. Every catch block can handle only one type of exception. If there may be more than one type of exception, we should implement several catch block. The most important feature of that mechanism, is that the function in which the error was detected, forces that calling function to handle it. Example: exm_exc Usage
Exceptions Any type of object can be thrown as exception. It is common to throw objects of a certain class family. If the exception is not caught, it will percolate up, (function after function) until reaching the main (where it will abort). Conclusions: Somewhere, sometime, someone is going to pay!!! No casting takes place!!! If more than one catch block fits the exception type, the first will be used (e.g. void* will take all pointers). catch (…) can catch all types: Can’t use the thrown object (WHY?) Should be last!!! An exception can be throws again (using “throw” command) Exception Handling
Exceptions The exception handling mechanism was only recently added to C++ as a standard component. Old libraries and classes may not use this mechanism. Memory Allocation – compiler dependent. Some throw an exception of type xalloc. Most simply return NULL. Dynamic Cast – compiler dependent. Some throw an exception of type bad_cast. File Handling – Unfortunately, C++ file handling classes does not use exceptions. MFC – A good example for exception usage. See documentation. Haw can we solve that problem? Compatibility
Exceptions A function can declare which exception it may throw Should appear in both prototype and implementation). This mechanism limits the exceptions that the function can throw and thus ensures that if we handle the declared exceptions, we are all covered. void f1() throw (ABC, int); can throw only objects of type ABC or int !!! void f2() throw (int); can throw only objects of type int !!! void f3() throw (); cannot throw any type of object !!! void f4() can throw any type of object !!! Declaring thrown Exceptions Important: A function that throw an exception that was not declare will not be detected in compilation or linkage, but would rather crush the application when this exception is thrown.
Exceptions Naturally, exceptions are very suitable for constructors (no return value!!!) If the C’tor did not terminate properly (an exception was thrown), the d’tor will not be activated. Allocated memory should be released before throwing the exception or in the catch block. but … if the exception was thrown from the C’tor of the derived class, the D’tor of the base class will be activated. (WHY?) Example: exc_ctor.cpp A few notes!!! Throwing Exceptions in C’tors and D’tors
Exceptions and Polymorphism As we recall, the catch block does not perform casting !!! But still…one exception may fit more than one catch block when pointers/references are involved (e.g. void* fit int*) We can use this property, combined with polymorphism: We will implement a catch block for the base class (by ref!!!) This block will also catch all the derived classes Using virtual functions, we can implement a general error handling code that will work according to he actual error !!! (This mechanism is commonly used in MFC. A full hierarchy of Exception class was implemented) Introduction
Exceptions and Polymorphism Note: To use polymorphism, the catch block should receive either a pointer or a reference to the object. using references is sufficient to support polymorphism and doesn’t require dynamic allocation. Example: exc_ploy.cpp A few Notes: CError is the base class of all the exception class. Thus, the catch block will catch also the derived class. Since DoManyThings() declares it throws CError, it may throw CError or any of it derived classes. The temporary exception objects lives until the end of the catch block where it was caught. Example
When ever we had a class that used dynamic allocation (e.g. String) we ran into the following two problems: The Problem: The Solution: A brief history of the dual referencing Who will free the allocated memory (when the object dies)? How to avoid dual referencing (when copying or assigning to another object)? If one object changes the content of the allocated memory it will affect all others object with the same reference If one object dies and free the allocated memory, it will leave all other with a dead reference. Implement a destructor which will free the allocated memory Implement a C.C. and assignment oper’ which will create their own copy + +
Reference Counting But…there may be a different (and more efficient) solution. Let’s allow two object to point to the same allocated memory (as a result of assignment of copy c’tor). To solve the problem of updating the memory: An object that is about to change the content of the shared memory, will first create its own copy (“Copy on Write”). To solve the problem of freeing an allocated memory: Only if the object that is being destructed detects that no other object is pointing to the same memory, it will free that memory. Advantages - Efficiency: Perform a copy operation only when needed. Use less memory. A different solution…
Reference Counting Copy on Write – Simple. When ever we want to change the content of the allocated memory, we will first allocate a new copy. Freeing Memory – We must implement a Reference Counting mechanism. We will count the number of object pointing to the same memory. Every time an object dies (or create its own copy) we deduce 1 from the counter. If the counter reaches 0 – we will delete that memory. And technically – we will add to the class String, a pointer to int, which will point to a shared reference counter. So, how can we implement that concept?