Chapter Twelve

Error handling

Errors can be reported and handled in a few different ways in a C++ program. Here, we will concentrate on the use of exception handling, which has many advantages compared to the other alternatives. By using exception handling it is possible to separate the error handling code from the normal flow of control and many different types of errors can be handled in one place. By allowing any amount of information to be passed with the exception, there is a better chance to make the correct decision when handling the error.

Different ways to report errors

Run-time errors can be reported in a few different ways in a C++ program. Throwing exceptions or returning status codes from functions are two possibilities. It is important to always check error conditions, regardless of how they are reported.

RULES
AND
RECOMMENDATIONS

Rec 12.1 Check for all errors reported from functions.

Rec 12.2 Use exception handling instead of status values and error codes.

Rec 12.1 Check for all errors reported from functions.

Rec 12.2 Use exception handling instead of status values and error codes.

In C++, the best way to report an unexpected error condition is to throw an exception.

 
throw EmcException("Fatal error: Could not open file");

Throwing an exception is very similar to a return statement. When a function returns, local objects will end their lifetime and their destructors will be called. The same thing happens when leaving a function by throwing an exception. A difference is that it is not obvious from reading the code which statement that will throw an exception, but is quite obvious where the function returns.

Throwing an exception is not the only way to report an error. Many programs reuse existing libraries written in C that report errors through status values and error codes instead of throwing exceptions.

A difference between these solutions is that it is not possible to ignore an exception. Unless there is a handler, a catch statement, that can handle the exception, the program will terminate. If that is the wrong behavior, the program must be modified.

It is important to handle exceptions, but it is even more important to always check status values returned from functions. If an error reported this way is ignored, there is no easy way of knowing what eventually made the program crash. Such programs must also be modified, but it is much more difficult to know where.

Checking status value

The socket() function is a UNIX library function that creates a communication channel between two processes. If the call succeeds, it returns a socket file descriptor that is >= 0 , otherwise -1 is returned.

 
// create socket
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd < 0) // check status value
{
   // ...
}

The negative return value is a status value that only tells the user that something did go wrong, but not the reason for failure. In this particular case, the global variable errno must be used to get a description of the error.

It seems natural to check status values returned from functions, but in reality there are huge amounts of code written that does not do these checks. The fact that status values can be ignored by the programmer is one of the reasons to why exception handling in most cases is a better way of reporting errors.

Using status values only works well if all functions along a call chain are given the chance to handle the error. This requires the programmer to mix code that represents the ordinary flow of control with code that is only run when an error is reported.

With exception handling it is possible to separate code that handles errors from the ordinary flow of control. Less code will need to be written since exception handling can be localized to one function along a call chain. It is also possible to handle many different exceptions with the same piece of code by specifying a handler for either an exception base class or with ellipsis ( ... ).

 
try
{  // ordinary flow of control
   f();
   g();
}
catch(...)     // handler for any kind of exception
{
   // error handling
}

An additional difficulty with status values is that constructors and some overloaded operators cannot return values, which means that a status value must either be passed as a reference argument or be stored by the object.

By using exception handling instead of status values, the functions will need fewer arguments and return values, which makes them much easier to use. Another advantage is that if you do not have any way of recovering from an error reported as an exception, you can simply ignore it and it will be propagated up along the call chain.

An additional benefit is that since an exception is an object, an arbitrary amount of error information can be stored in an exception object. The more information that is available, the greater the chance that the correct decision is made for how to handle the error.

Throwing an exception

One way to encapsulate the UNIX system calls is to provide a wrapper function that throws an exception instead of returning a status value.

 
class EmcException
{
   public:
      // ...
      // EmcException objects can be printed
      friend ostream& 
      operator<<(ostream&, const EmcException&);
      // ...
};

class EmcSystemException : public EmcException
{
   public:
      EmcSystemException(const char* message);
      // ...
};

int emcSocket(int family, int type, int protocol) throw(EmcSystemException)
{
   // create socket
   int socketfd = socket(family, type, protocol);
   if (socketfd < 0)// check status value
   {
      throw EmcSystemException("emcSocket");
   }
   return socketfd;
}

A better solution is to encapsulate the calls inside a class that represents what the socket is used for. By doing that, errors reported by functions like socket() can be translated into exceptions that are more meaningful to the user, and can also encapsulate all reasons why a particular member function failed. This will also allow the user to modify the implementation and to replace sockets with any other mechanism for inter-process communication, without revealing such changes to the user. We have not done that because we wanted to keep the example simple.

When to throw exceptions

A programmer can throw an exception anytime, so rules are needed for when exceptions are thrown so that both the user and the supplier of class libraries can write code that is robust and correct.

RULES
AND
RECOMMENDATIONS

Rec 12.3 Only throw exceptions when a function fails to perform what it is expected to do.

Rec 12.4 Do not throw exceptions as a way of reporting uncommon values from a function.

Rule 12.5 Do not let destructors called during stack unwinding throw exceptions.

Rec 12.6 Constructors of types thrown as exceptions should not themselves throw exceptions.

Rec 10.6 , how to describe what a function is expected to do.

Rule 12.8 , classes that must have a destructor.

Rec 12.3 Only throw exceptions when a function fails to perform what it is expected to do.

When should an exception be thrown? It is possible to throw exceptions whenever a function encounters an unusual case, but we do not recommend that since too frequent use of exceptions will make the control flow difficult to follow.

It is appropriate to use exceptions as a way to report unexpected errors. What is unexpected depends on the class specification and when the error is detected. The user and the implementor often have different views of what is unexpected. If preconditions and postconditions are used to specify behavior of member functions, it is possible to be more precise.

We think that an exception should only be thrown to report an unexpected error to the user. We must give the user a chance to handle an error that could not have been prevented by a precondition check.

Such exceptions are part of the class interface and tells the user in what way the function could not fulfil its obligation to make the postcondition valid.

Exceptions thrown for any other reason than this are questionable, but not completely forbidden.

Not following this recommendation means that exceptions are sometimes thrown even when the user could have prevented them from being thrown. A precondition violation is a good example.

Since it is the user's obligation to make the precondition valid, such errors are only found in incorrect programs. What is the best way to handle such errors? To recover from the error and to let the program continue, or rewrite the program? We prefer the second alternative and recommend you to check preconditions only as a way to find bugs in your program.

It is useful to check the precondition since that prevents the user from writing incorrect code, but if we assume that incorrectly written programs should be corrected, how to report precondition errors are less important. If an exception should be thrown or the program should terminate by calling abort() is a matter of taste and depends on the situation. Exception handling allows the program to terminate in a more controlled manner.

Member function with precondition

When initializing an EmcString object with a char -array, a precondition is that a non-null pointer must be passed as argument.

The implementation does not throw an exception, since the user can prevent a null pointer to be passed as parameter. Here, it is the user's obligation to make sure that the member function can do what it is expected to do.

An EmcString object stores a pointer to a char -array that is allocated with new . The user cannot possibly check beforehand that new might fail to allocate the necessary memory needed for the allocation, so the implementation must report the error by throwing an exception.

 
EmcString::EmcString(const char* cp) throw(bad_alloc)
: lengthM(strlen(cp))
{
   // PRECONDITION: cp != 0

   // operator new[]() will throw bad_alloc
   // if allocation fails
   cpM = new char[lengthM + 1];
   strcpy(cpM, cp);
}

Rec 12.4 Do not throw exceptions as a way of reporting uncommon values from a function.

A consequence of the recommendation that exceptions should only be thrown if a function fails to do what it is expected to, is that exceptions should not be used as a way of reporting uncommon values from a function.

It is important to remember why exceptions are a bad choice in these situations. If an exception is thrown, that exception must be handled, or the control flow of the program will change in a way that cannot be predicted. Throwing an exception for the sole purpose of changing the control flow is therefore not recommended.

Your code can be difficult to understand if you throw exceptions in many different situations, ranging from a way to just report unusual threads in your code to reporting fatal run-time problems. Exception handling is also often a very inefficient way to change the control flow in a program, compared to passing along error codes.

Returning special value to report failure

The find() function in the standard library is a good example of a function that could fail, but for which throwing an exception is inappropriate.

The standard library uses iterators to traverse through collections of objects. The iterators are modeled after pointers, and ordinary pointers are therefore a special case of iterators.

An input iterator is a special kind of iterator that allows you to read one element at the time in a forward direction only. If such an object is assigned to an element in a collection, it will eventually, after being incremented a number of times, be equal to the iterator pointing at the last element in the collection.

 
template<class InputIterator, class T>
InputIterator
find(InputIterator first, InputIterator last, 
     const T& value);

The function find() is defined to return the first iterator between first and last element (but not counting last itself) that points to a T equal to value . If no such value is found, it will return the last iterator. It is quite common not to find what you are looking for, so it is not reasonable to call it a programming failure if that happens. Therefore find() is defined to return last if value was not found in the sequence.

Rule 12.5 Do not let destructors called during stack unwinding throw exceptions.

There are a few places where exceptions should not be used to report errors. Inside destructors is one such particular place.

A try -block defines both a scope and a set of exception handlers. Before continuing the execution inside a handler, the program will leave the scope of the try -block. This means that destructors for local variables inside the try -block must be run to properly end their life time.

If an exception is thrown during this process and not handled by the destructor, the library function terminate() will be called. This function will terminate the program. If that happens, there is a good chance that some external resources managed by local objects have not been released, which could mean that the program cannot be restarted without first manually releasing such resources.

There are two ways to avoid this. Either you make sure not to call code that might throw exceptions inside destructors or you catch all exceptions thrown in destructors. The second alternative requires some additional programming, since you must add a try -block with exception handlers to the implementation of the destructor.

A problem is that you may want to allow the user to handle exceptions thrown under normal circumstances. A recent addition to C++ is the function uncaught_exception() which will report true if exceptions are handled, and false if they are not. If your compiler supports this function, then you can check if it is OK to rethrow the exception. If it is not supported, you should ignore all exceptions thrown inside the destructor.

Preventing exceptions inside destructors

Logging is useful if you want to know what made a program crash. It can however slow down a program since output must be written to a file or the console. One way to improve performance is to cache the log messages in memory and only write them to a file when something unexpected happens, e.g. when an exception is thrown.

The class EmcLog is used to implement such a scheme. The class stores the log messages and writes them to a log file after a call to the member function flush() . The idea is to allocate objects of this class on the stack and to use the function uncaught_exception () inside the destructor to check if an exception has been thrown or not. If an exception has been thrown, we append to the log file.

 
class EmcLog
{
   public:

      class CouldNotOpenFile : public EmcException
      {
         public:
            CouldNotOpenFile(const char* file);
      };

      EmcLog(const char* filename);
      ~EmcLog();

      void message(const EmcString&); // store log message
      void flush() throw(CouldNotOpenFile);               
	                                        // append to log file

      // ...

   private:
      EmcLog(const EmcLog& i);           // Non-copyable
      EmcLog& operator=(const EmcLog& i);

      EmcQueue<EmcString> messageCacheM; // log messages
      const char*         filenameM;     // log file
};

EmcLog::~EmcLog()
{
   if (uncaught_exception())
   {
      flush();
   }
} 

We must also call uncaught_exception() inside flush() , since this function throws an exception if it is unable to open the log file. Since an exception must not propagate from the destructor, such an error must be ignored when flush() is called by the destructor.

 
void EmcLog::flush()
{
   ofstream out(filenameM, ios::app);
   if (!out && !uncaught_exception())
   {
      throw EmcSystemException("EmcLog::flush()");
   }
   // write messages to log
   // ...
}

Rec 12.6 Constructors of types thrown as exceptions should not themselves throw exceptions.

Another place where exceptions should be prevented from slipping out is inside the constructors of objects thrown as exceptions.

The problem here is that if the constructor throws an exception, then the user would get the wrong exception to catch. The user may catch the exception, and even try to recover from the problem, but the user is actually trying to handle another error. The real problem will be lost and forgotten.

For copy constructors, there is another reason. The exception object will be copied to an area managed by the exception handling system before leaving the scope in which the throw is done. If this copy fails, terminate() will be called.

Exception class constructor

The exception class EmcException has a constructor with a const char* parameter. It seems natural to have a string data member to store that value. Most string classes allocate memory with the new operator. This means that if the class has such a data member, the constructor of this class will throw the standard exception bad_alloc if memory allocation fails.

A way to avoid that would be to limit the size of the string. Such a solution has the advantage of being exception safe, but you have to make sure that the allocated string is big enough.

 
class EmcException
{
   public:
      EmcException(const char* message);
      // ...

   private:
      enum { maxSizeM = 100 };

      int   lengthM;
      char  messageM[maxSizeM+1];
};

EmcException::EmcException(const char* message)
{
   size_t actualLength = strlen(message);
   lengthM = min(maxSizeM,actualLength);
   strncpy(messageM, message, lengthM);
   messageM[lengthM] = '\0';
} 
 

Exception-safe code

It is necessary to prevent memory leaks and other errors that are related to how resources are acquired and released. By managing all resources with objects it will be less difficult to write code that properly manages resources.

RULES
AND
RECOMMENDATIONS

Rec 12.7 Use objects to manage resources.

Rule 12.8 A resource managed by an object must be released by the object's destructor.

Rec 12.9 Use stack objects instead of free store objects.

Rec 12.10 Before letting any exceptions propagate out of a member function, make certain that the class invariant holds, and if possible leave the state of the object unchanged.

Rec 5.11 , when to implement copy constructor, copy assignment operator and destructor.

Rec 10.6 , definition of class invariant.

Rec 12.7 Use objects to manage resources.

A resource is something that more than one program needs, but for which there is a limit for how much that is available. Good examples are memory and other operating system resources like sockets, file descriptors, drawing contexts, shared memory and database locks. The most important to manage are those that are not released when the program terminates.

It is essential to correctly acquire and release resources. Unless you acquire a resource for the whole lifetime of the program, a resource should be acquired and released within a block of code. It is common to have a function that is called at the beginning of the block and another function that is called at the end of the block.

call function to acquire resource

use the resource

call function to release resource

The question is how to make sure that the statements for acquiring and releasing the resource are both run. What is difficult is that the control flow of a C++ program is not sequential, since a function could return either the normal way or by throwing an exception.

A fundamental idea behind the C++ exception handling is that resources should be allocated in the constructor and deallocated in the destructor of a class. This is often called "Resource acquisition is initialization". Another way to say this is that resources should be managed by objects.

It is convenient to use the constructor and the destructor for this purpose, since they are automatically called when the objects start and end their lifetime. No additional function calls are needed to properly manage the resource. It is also the best way, since destructors are the only member functions that are called before leaving a scope after an exception has been thrown.

If your code is a mix of application logic and error handling code, this is probably a consequence of not having exception safe classes. It should always be a goal to separate error handling code from the application control flow.

Rule 12.8 A resource managed by an object must be released by the object's destructor.

You should always release a resource in the destructor. If any other member functions would need to be called, you would perhaps have to catch the exception and propagate it a number of times before handling it. This is a much more complex solution since additional code must be written.

Rec 12.9 Use stack objects instead of free store objects.

You should also question how you allocate objects. C++ has both objects with static, automatic and dynamic storage duration. Objects created with new are most expensive to allocate and most difficult to use. Whenever possible, you should create an object on the stack instead of with new . Stack objects are less expensive to allocate and there is no risk of getting any memory leaks as long as you only use exception safe classes.

You only need to create an object with new if the life-time is not controlled by you, not just because you need a pointer to the object.

Exception handling has made it even more difficult to manage free store objects. Each free store object must always be accessible through either a static pointer or an object on the stack that owns the object.

It is dangerous and inconvenient to have only local pointers to objects allocated with new . If a local pointer is the only way to access an object created with new , your code will not be exception safe, unless you have a try -block that catches all possible exceptions.

Unsafe memory allocation

The most fundamental resource to manage in C++ programs are dynamically allocated memory. The most obvious example is a string class and we have earlier in the book seen examples on how to write such a class.

The following code is unsafe since it contains a memory leak. The problem is that the delete statement is not reached if an exception is thrown within the function.

 
void f()                 // Not recommended
{
   int* ip = new int(1); // create int with new
   g(*ip);
   // memory leak if g() throws exception
   delete ip;            // not reached
} 


void g(int i)
{
   throw i;              // Not recommended to throw int
}

Having a try -block to manage memory

It is possible to rewrite our previous example so that the memory leak is avoided without introducing any new classes. The function should have a try -block with a handler that catches all possible exceptions.

 
void f()                  // Not recommended
{
   int* ip = new int(1);  // create int with new
   try
   {
      g(*ip);
      // memory safe even if g() throws exception
      delete ip;          // not reached
   }
   catch(...)             // catch any exception
   {
      delete ip;
      throw;              // Rethrowing the exception
   }
} 
 

Exception safe allocation of free store objects

The best way to manage objects allocated with new is to have a local object that manages the memory instead of a pointer and a delete statement. You code will be shorter and less difficult to write.

We recommend you to use the class template, auto_ptr , supplied by the C++ standard library.

 
void f()
{
   auto_ptr<int> ip = new int(1); // create int with new
   g(*ip);
   // memory safe even if g() throws exception
}

If you want to keep control of the deletion of the object managed by the auto_ptr , you must explicitly call release() to tell the auto_ptr to give up ownership of the object. If you do not do that, the auto_ptr will delete the object when its destructor is run.

Rec 12.10 Before letting any exceptions propagate out of a member function, make certain that the class invariant holds, and if possible leave the state of the object unchanged.

Throwing an exception should not damage the state of your objects. If possible, preserve the state of the current object before leaving the scope of a member function by throwing an exception. If that is not possible, try to restore the state so that the object's destructor is safe to call. By doing that there is a greater chance that the program can recover from the exception, since if the current object is a local object its destructor will be called. As said before, such a destructor must not throw exceptions or fail in any other way.

Here we discuss state only after the object has been initialized. When exceptions are thrown by constructors, destructors are only called for member objects that are completely initialized. Only these need to be in valid states when leaving the constructor - not the complete object.

All constructors should leave the object in a valid state so that its destructor can be called without any errors. That guarantees successful clean-up of member objects when leaving the scope of the constructor.

When designing classes you should try to figure out which operations that could throw exceptions, and then minimize the amount of time that the object is in an invalid state. If it is possible, modify the state of the object only after all dangerous functions has been called. If that is not possible, either make it possible to restore the state of the object or give the object a default value before throwing the exception.

When writing templates you must decide what operations that are allowed to throw exceptions. If you do not make any such assumptions, an exception could be thrown in a situation where the state of the object is invalid.

If it is possible, whenever a member function modifies the state of an object, avoid changing the state of the actual object and instead modify a copy of the state. If we can switch the state of the object without getting any exceptions, a template can allow any exceptions to be thrown when updating the copy, not the original, thereby keeping the state of the original object unchanged.

Better performance can be achieved by making stronger assumptions about what exceptions that can be thrown, but then the class will be less reusable. As always there is a trade-off between flexibility and performance.

Exception safe copy assignment operator

The template EmcStack uses a built-in array, vectorM , to store copies of objects. The pointer topM stores an index to the next element in the array to assign. The data member allocatedM stores the number of currently allocated objects, and is always a positive number.

 
template<class T>
class EmcStack
{
   public:
      enum      { defaultSizeM = 100 };
      
      EmcStack(int size = defaultSizeM);
      EmcStack(const EmcStack& s);
      ~EmcStack();
      EmcStack& operator=(const EmcStack& s);
      // ...
      bool      empty() const;
      const T&  top() const;
      void      push(const T& i);
      const T&  pop();
      
   private:
      unsigned  allocatedM;
      T*        vectorM;
      int       topM;
};

We want to provide an exception safe implementation of the copy assignment operator for EmcStack . Our strategy is to make all dangerous operations before modifying the state of the object, so that the state will be valid even if an exception is thrown.

In order to avoid memory leaks, we also use an object of the class EmcAutoArrayPtr<T> to manage memory. EmcAutoArrayPtr is a template that is similar to the class auto_ptr in the standard library, but manages arrays of objects instead of individual objects.

 
template<class T>
EmcStack<T>& EmcStack<T>::operator=(const EmcStack<T>& s)
{
   if (this != &s)
   {
      // operator new may throw bad_alloc
      EmcAutoArrayPtr<T> newVector(new T[s.allocatedM]);
      
      // copy elements
      for (int i = 0; i < s.topM; i++)
      {
         newVector[i] = s.vectorM[i];
      }
      delete [] vectorM;
      
      // assign to object
      topM       = s.topM;
      vectorM    = newVector.release();
      allocatedM = s.allocatedM;
   }
   return *this;
}

If memory allocation would have been costly, we could have tried to optimize by copying to existing storage already used by the object, as was done in EXAMPLE 5.12 . Such an implementation would however be much more difficult to make exception safe. If an exception is thrown when assigning to an element of the objects representation, the state of the object will be undefined and probably corrupt.

Exception types

Exception handling makes it possible to localize error handling to fewer places in the code. The number of try blocks should not have to grow exponentially with the size of the program. Exception classes should be organized in hierarchies to minimize the number of exception handlers.

Exception hierarchies allow for object-oriented error handling, i.e. you can use dynamic binding when handling errors. This means that the same handler can be used for different types of exceptions. This will make the code more readable and easier to maintain.

RULES
AND
RECOMMENDATIONS

Rec 12.11 Only throw objects of class type.

Rec 12.12 Group related exception types by using inheritance.

Rec 12.13 Only catch objects by reference.

Rule 7.6 , why objects are passed by reference.

Rule 10.8 , behavior of derived classes.

Rec 12.11 Only throw objects of class type.

An object can be thrown if it can be copied and destroyed. This makes it possible to throw values of built-in types, pointers, arrays or objects. You should only throw objects of class type, since otherwise it will not be possible to distinguish errors by the type, only by the value. There is nothing in the language to prevent a value to represent many things, but a type name must be unique within a program.

If we throw a general-purpose type, such as an int , the value would have to represent exactly one type of error, or there would be a risk that the wrong error is handled. We would have to use a value that is globally unique, a solution that makes it difficult to add new classes or to use new class libraries.

The exception type should instead always represent the type of error, and it should be a class that is used for exception handling only.

An additional benefit of throwing objects is that they can contain any amount of data. You can have a data member that stores a description of the error and you can print that description inside the handler.

Throwing object of built-in type

The socket() function has many reasons for failure, each one of them represented as an integer value. For example, EACESS is returned if the function is denied permission to create the socket, and ENOMEM is returned if there is no available memory.

Suppose you would like to translate these error codes into exceptions. It is possible, but not recommended, to throw an int containing the error value. The problem with this approach is that you cannot catch different objects, in this case different integers, only different types. With an integer approach like this you would therefore be forced to have one single catch clause with a big switch statement for how, or if, an error should be handled, depending on the integer value. What is even worse, this solution only works if you can know from where the exception originates. Nothing prevents two functions from throwing the same value to represent two different errors.

Rec 12.12 Group related exception types by using inheritance.

Rec 12.13 Only catch objects by reference.

A try block could have as many handlers as there are exception types, but it is good to limit the number of handlers.

You can group related exception types by using inheritance. This is necessary when you want to handle many different types of exceptions the same way.

It is a good idea to catch a reference to a base class, so that the user can ignore the exact type of the exception that was thrown.

An important aspect here is that it is possible to derive new classes without affecting the user's code. The handler for the base class will handle exceptions of derived classes. Instead of having many handlers for each derived class, you can have a handler for a base class. In the catch clause we are supposed to try to handle an error, so it makes sense to group exception classes in hierarchies according to how they can be handled.

Another reason to why exceptions should be caught by reference is that you can loose information when a derived class object is copied to a base class object instead of being passed by reference. The same thing that could happen when passing objects by value to a function.

It can be useful to have nested exception classes. If you derive from both that class and from a general purpose exception class, this will allow you to organize your handlers not only based on error type, but also on where the exception was thrown. Inheritance is used to control type matching rather than to create specializations of the base class.

Inheritance of exception classes

It is good to have a general exception class at the top that allows you to print a description of the error. Most users are satisfied with knowing what went wrong and would only have one handler for a whole hierarchy of exception classes.

In our examples we have used the class EmcException , that stores strings that describe the error condition.

 
class EmcException
{
   public:
      EmcException(const char* message);
      
      // EmcException objects can be printed
      friend ostream& 
      operator<<(ostream&, const EmcException&);
      
   protected:
      // hook for derived classes
      virtual ostream& printOn(ostream& o) const;
      
   private:
      enum { maxSizeM = 100 };
      
      int  lengthM;
      char messageM[maxSizeM];
};

The class provides a virtual member function printOn() that can be overridden by derived classes.

 
ostream& EmcException::printOn(ostream& o) const
{
   o << messageM;
   return o;
}

ostream& operator<<(ostream& o, const EmcException& e)
{
   return e.printOn(o);
}

If an object of the class EmcException or any class derived from it is handled, the message printed will both depend on the type of the exception and the message stored by the object.

We have also used the class EmcSystemException that is derived from EmcException .

 
class EmcSystemException : public EmcException
{
   public:
      EmcSystemException(const char* cp);
      // ...
   protected:
      virtual ostream& printOn(ostream& o) const;
   private:
      static const char* const headerM;
};

It overrides printOn() so that a header is provided for each error message. The global variable errno is used as index in the table of error messages for the UNIX system calls, sys_errlist .

 
const char* const 
EmcSystemException::headerM = "System call failed: ";

extern char* sys_errlist[]; // Table with error messages
                            // for UNIX system calls

ostream& EmcSystemException::printOn(ostream& o) const
{
   o << headerM << " " << sys_errlist[::errno] << ": ";
   return EmcException::printOn(o);
}

Handling many exceptions with one handler

A handler for EmcException can be used to handle an EmcSystemException , since the latter class inherits from EmcException .

 
try
{  // ordinary flow of control
   int socketfd = emcSocket(AF_UNIX, SOCK_STREAM, 0);
   // ...
}
catch(EmcException& e) // handler for any exception class
                       // derived from EmcException
{
   cerr << e << endl;
   // ...
} 
 

Error recovery

Sometimes exceptions of unknown types may propagate through your code. It is important to know which of these you should catch, and which ones you should let the user handle.

RULES
AND
RECOMMENDATIONS

Rule 12.14 Always catch exceptions the user is not supposed to know about.

Rec 12.15 Do not catch exceptions you are not supposed to know about.

Rec 10.6 , Rec 12.16 , specifying exceptions for a class.

Rule 12.14 Always catch exceptions the user is not supposed to know about.

Hidden implementation details is an important property of well written programs, since it gives you the possibility to make changes without affecting the user.

Imagine a hierarchy of libraries where some libraries are implemented on top of other libraries. To be able to change or replace lower level classes without affecting the user, you must catch all exceptions that the user is not supposed to know about. Otherwise an exception of a class unknown to the user could terminate the program or be caught by a handler with a ... parameter list. In either case, nothing can be said about what caused the exception to be thrown. All exceptions that reach the user should be known to the user, since that will make it possible to explain why the exception was thrown and how to prevent it from being thrown. You should try to avoid writing programs that simply crashes without any proper indication of what went wrong.

Rec 12.15 Do not catch exceptions you are not supposed to know about.

There are on the other hand exceptions that may propagate through your code which you should not catch or translate. The most obvious example is exceptions that might be thrown from template parameters.

The template designer must specify under what circumstances a variable of a type given as template parameter is allowed to throw exceptions. It is practically very difficult, if not impossible, to write templates that can be instantiated with a type that throws exceptions in places that are not known in advance. These exceptions should in most cases be propagated to the user of the template, since only the user code knows what exceptions to expect.

There are other cases where you may use code which can throw unknown exceptions. The user might, for example, supply a pointer to a sorting or hash function, which you will use inside your code. In such cases you should as well let the supplier of the function take care of all the exceptions that might be thrown.

Exception specifications

Exception specifications are used to document what exceptions that are thrown from a function. We recommend you to use them as much as possible.

RULES
AND
RECOMMENDATIONS

Rec 12.16 Use exception specifications to declare which exceptions that might be thrown from a function.

Rec 12.3 , when to throw exceptions.

Rec 12.16 Use exception specifications to declare which exceptions that might be thrown from a function.

Exceptions are part of the class interface and must be handled by the user when they are thrown. The language gives you an option to declare the exceptions thrown by a function. If a function does not have an exception specification, that function is allowed to throw any type of exception.

We recommend you to use exception specifications as much as possible. Since they are part of the language, the compiler will check that the exception classes exist and are available to the user.

It is a program bug if a function with an exception specification throws an exception that has not been specified. If that happens, the default is to either terminate the program or, if the exception specification includes bad_exception , to throw an object of that class instead. You should avoid this situation if you can.

A consequence of the fact that template functions should propagate exceptions is that a template function should only rarely have an exception specification. It should only have it when the exact set of exception types that can be thrown are known in advance. A template function should probably not have an exception specification if the type of the exception thrown depends on a type argument.

Exception specification

 
char& EmcString::at(size_t pos) throw(EmcIndexOutOfRange)
{
   if (pos > lengthM)
   {
      throw EmcIndexOutOfRange(pos);
   }
   // ...
}