C++ course

Exception handling

Objectives

  • Exceptions - motivation
  • Throw
  • try..catch
  • inheritance
  • Standard exceptions

Motivation

In the real world exceptional cases must be handles 

double divide(double a, double b)
{
    return a/b;
}

int main()
{
      divide(10,0); // Will cause division by zero
}

In Procedure languages that would have been done as follows

double divide(double a, double b)
{
    if (b == 0) {
        cerr << "Division by zero" << endl;
        exit(-1);
    }
    return a/b;
}

Motivation

The procedural way has several problems with it

  • ​The error handling is performed by the callee, which has less information than the caller.
  • The function's implementation is contaminated by error handling
  • In some case the error handling contaminates both the caller and the callee
  • It is not compatible with the object model (see next slide)
class Person
{
   string m_name;
   bool   m_valid;
   
   public:
     Person(const string& name):m_name(name), m_valid(true) { if (m_name == "") m_valid = false; }
     
     void doSomething() const {
        if (m_valid) {
           // Do something..
        }
     }
};

Exceptions to the rescue

Exceptions can separate code from error handling

class Person
{
   string m_name;
   
   public:
    Person(const string& name):m_name(name){  if (m_name == "") throw "Person must have a name"; }
     
    void doSomething() const {
           // Do something..
    }
};

int main()
{
    try {
      Person p("");
      p.doSomething();
    } catch(const char* exc) { cout << "Exception caught: " << exc << endl; }
}

There are 3 new keyword to support exception handling

  • try - introducing a code block that may throw an exception
  • catch - catching an exception of a type if it was thrown
  • throw - throw an exception of a type

Throwing exceptions 

When an error is encountered, an exception is thrown.

The exception can be of any type

throw "abc";          // const char* is thrown
throw string("abc");  // a string is thrown
throw 1;              // an integer is thrown
throw 5.2;            // a double are thrown
throw Person("Me");   // A Person object is throw

The function stack will unwind until the exception is caught via a try...catch clause. if there is no appropriate catch clause and the stack is completely unwind the program will call terminate(), terminating the programs execution.

try...catch

A try is declared when we are interested in handling some or all of the potential errors. Each try is followed by a list of catch clauses, each catch can handle a specific type.

try {
  // Some error prone functionality
} 
catch (const double& d) { /* caught a double */ }
catch (const string& s) { /* caught a string */ }
catch (...) { /* catch ANY exception */ }
class Person
{
   /* A Class that may throw an exception
};

void useNamelessPerson()
{
    Person p("");
}

int main()
{
    try {
        useNamelessPerson();
    }
    catch (const char* esc) { cout << "ERROR: " << esc << endl; }
}

Terminating

As mentioned above the terminate function will be called once the entire stack is unwind and no relevant catch clause was found.

 

We can provide our own terminate function.

void theTerminate()
{
    cout << "Reached my terminate" << endl;
    exit(1);
}

int main()
{
   set_terminate(theTerminate);
   useNamelessPerson();
}

Exception declaration

A function or a method can declare what exceptions they may throw, in case an undeclared exception is thrown from within the function/method during runtime the unexpected() function will be called exiting the program immediately.

void foo() throw (string, int)
{
    throw string("String");
}

Just like terminate() we can supply our own unexpected() function via the set_unexpected(...) function.

void theUnexpected()
{ 
    cout << "In my unexpected function" << endl;
}

int main()
{
    set_unexpected(theUnexpected);
    ...
}

Note: This is deprecated in C++11

Re-throwing

Sometimes it makes sense to perform  error handling but still propagate the exception (rethrow it)

try {
   // Code that can throw an exception
} catch (const string& str) {
  // Do some cleanup
  throw;  // re-throws the exact same exception
}

Exceptions and inheritance

When catching an object that can have several types (Due to inheritance) make sure that the the catch clauses are from the most specific to the more general class

class Base  {};
class Derived : public Base {};


int main()
{
    try {
        foo();
    }catch (const Derived&  d) { cout << "Caught Derived" << endl; }
     catch (const Base&     b) { cout << "Caught Base "   << endl; }  
}

Standard exceptions

Usually we will throw an exception that will contain some relevant information that we can use like the error, line number or even some relevant values. The standard introduced some exceptions.

The exceptions encode the type of error in the type itself, while additional information will be part of the type and is usually accessible via the what() method.

Standard exceptions - cont

Standard exceptions are used in exactly the same manner as primitive types or user defined exceptions, but, you will have to include stdexcept header file for compilation.

#include <stdexcept>

void foo() {
  throw runtime_error("Not implemented yet");
}

int main()
{
  try {
      foo();
  } catch (const runtime_error& e) { cout << "Exception: " << e.what() << endl; }
}

If you need to add some more attributes, you can inherit from any standard exception and add to it.

Made with Slides.com