Common Runtime Errors





Error Handling Options





Errors in Encapsulated Code

Low level code should not handle errors:





Error Detection vs Error Handling





Exception handling...





Throwing an Exception

When an error is detected, throw an object.

double quotient(int num, int den) { if (den == 0) throw DivByZeroEx(); // default constructor return static_cast<double>(num) / den; }



Exception Classes

Objects thrown should be from specially defined classes:

class DivByZeroEx { public: DivByZeroEx ( ) : m_message ("attempted divide by 0") { /* no code */ } const string& what ( ) const { return m_message ; } private: const string m_message; };



try and catch





try/catch Example

int main() { // get numerator and denominator from user try { result = quotient(numerator, denominator); cout << "The quotient is: " << result << endl; } catch (DivByZeroEx& ex) { cerr << "Exception occurred: " << ex.what() << endl; } // code continues here }



What really happens

  1. A temporary copy of the throw operand is created and initialized.

  2. Control immediately exits the try block (all automatic objects defined within the try block are destroyed).

  3. Control proceeds to the appropriate catch block (if any).

  4. Copy of the throw operand passed to the catch block parameter.

  5. Code in catch block executed.

  6. Control passes to the first line of code after the last catch block.




Multiple catch Blocks

try { // code that might throw an exception } catch (ExceptionObject1& ex1) { // exception handler code } catch (ExceptionObject2& ex2) { // exception handler code ... } catch (ExceptionObjectN& exN) { // exception handler code } catch (...) { // default exception handler code }



Rules for catch





Exceptions and Nested Function Calls

If exception is not caught in the current scope:





Rethrowing an Exception





Exception Classes





Exception Specification

A function's prototype may specify which exceptions the function throws.

// Throws only the DivideByZero exception void Divide (int dividend, int divisor) throw (DivideByZero) ; // Throws either DivideByZero or AnotherException void Divide (int dividend, int divisor) throw (DivideByZero, AnotherException); // Promises not to throw any exception void Divide (int dividend, int divisor ) throw ( ); // Can throw any exception it wants void Divide (int dividend, int divisor );



Exceptions & Constructors





Exceptions & Destructors





Don't throw exceptions in a destructor





Standard C++ Library Exceptions

The C++ has predefined exceptions. To use these, #include <stdexcept>.





Exceptions and Inheritance





Exception Safety





Example: Unsafe Assignment

FredArray& FredArray::operator=(const FredArray& rhs) { if ( this != &rhs ) { delete [] m_data; // 1 m_size = rhs.m_size; // 2 m_data = new Fred [ m_size ]; // 3 for (int j = 0; j < m_size; j++ ) // 4 m_data[ j ] = rhs.m_data [ j ]; // 5 } return *this; } What happens if an exception is thrown in Line 3 or 5???



The Fix: Safer Assignment

FredArray& FredArray::operator=(const FredArray& rhs) { if ( this != &rhs ) { // code that might throw exceptions Fred *tempArray = new Fred[rhs.m_size]; for ( int j = 0; j < rhs.m_size; j++ ) tempArray[j] = rhs.m_data[j]; // code that will not throw exceptions delete [] m_data; m_size = rhs.m_size; m_data = tempArray; } return *this; }



Three Exception Guarantees

If an exception is thrown, ...

  1. Weak (Basic) Guarantee:

  2. Strong Guarantee:

  3. NoThrow Guarantee:




A Guideline

The C++ standard library follows this guideline:

A function should always support the strictest guarantee that it can support without penalizing users who don't need it.