12Exception Handling

When we write application software, we should ensure its correctness and robustness. In other words, the software should run correctly when conditions and user operations are correct. When an unexpected problem arises or users operate improperly, the program should behave properly and reasonably without causing system failure or catastrophic consequences. Because conditions and user operations cannot be guaranteed, we should consider all possible circumstances and take effective measures accordingly. This precautionary measure for dealing with runtime errors is called exception handling.

12.1Basic Concepts of Exception Handling

Some unavoidable runtime errors can be anticipated, including insufficient memory, missing files on hard disks, and discounted printers. These errors are caused by the program running environment. When such errors arise, we should allow users to correct the environmental errors and allow the program to continue to run and we should show some error messages. This is the task of exception handlers.

In large-scale software, because each function has a specific purpose and the relationships between functions are complex, an individual function does not have the capacity of handling errors. When an error is detected by a function, it may trigger (or ‘throw’) an exception condition and expect its caller to handle this error. If the caller cannot handle the error, it should pass it to a higher-level caller. This propagation will continue until the exception is handled. If an exception cannot be handled by the program, it will be passed to the C++ running system, which will then terminate the program. Figure 12.1 shows the direction of exception propagation.

Fig. 12.1: Exception propagation direction.

The exception handling facility of C++ does not put the triggering (or throwing) and handling of exceptions in the same function, so that lower-level functions can focus on solving specific problems rather than dealing with exception handling unnecessarily. Higher-level callers can handle different exceptions at the right level.

12.2The Implementation of Exception Handling in C++

C++ provides internal support for handling exceptions. The try, throw, and catch functions are the mechanism used for implementing exception handling. With C++ exception handling, the program can pass unexpected events to a higher-level execution context that can better recover from these unexpected events. The following section shows the proper syntax of exception handling functions.

12.2.1The Syntax of Exception Handling

The syntax of the throw function is shown as follows:

try block syntax:

If a program finds an exception that it cannot handle, it can use the throw expression to ‘throw’ this exception to its caller. The operands of throw are used to express exception type in a similar way to the operands of the return statement. If a program needs to throw an exception more than once, it should use different types of operands to differentiate different errors, and values of operands cannot be used to differentiate distinct exceptions.

The compound statement after the try clause is a protective segment of the code. If a program or a function is expected to encounter exceptions, we should put it after a try clause. If an exception occurs during the running of a program or a function, the throw expression will throw this exception to a handler.

The compound statement after the catch clause is the exception handler, which can “catch” exceptions thrown by the throw expression. The declaration part of exception types specifies the type of the exception handled by the clause. It is similar to formal parameters of a function and it can be values of some types or references. The type can be any valid data type, including classes of C++. When the exception is thrown, the catch clause will be checked one by one. If the exception type declaration of a catch clause matches the exception type being thrown, this exception handler will be executed. If the exception type declaration is an ellipsis (...), the catch clause will handle exceptions of any type, and this handler must be the last segment of a try block.

The execution process of exception handling is as follows:

  1. Control reaches the try statement following the normal order, and then executes protective segment in the try block.
  2. If there is no exception in the execution of a protective segment, the catch clause after the try block will not be executed. The program will continue from the statements after the last catch clause of the try block.
  3. If there is an exception thrown during the period of executing a protective segment or any function called by the protective segment (direct or indirect call), the system will create an exception object using a throw operation (this implies that a copy constructor may be contained). At this point, the compiler will find a catch clause from a higher-level execution context that can handle the exception (or a catch handler that can handle exceptions of any type). The catch handler will be checked in the order of appearance after the try block. If there isn’t any proper handler, it will continue to check the try block of the outer layer. This goes on until the outermost try layer is checked.
  4. If no matched handler is found, the running function terminate will be called automatically. The default function of terminate is to call the abort terminator.
  5. If a matched catch handler is found, it will be executed. Then the program jumps to statements after the last handler.

Example 12.1: Handle Exception of Dividing by Zero.

The result of the program is as follows:

We can see from the result, when executing the following statement, the exception of dividing by zero will happen in function Div.

After the exception is thrown, it will be caught in the main() function. The exception handler outputs some relevant information, and then the program jumps to the last statement of the main function and outputs “that is ok”. However, the following statement in function Div will not be executed.

The order of the catch handler is very important, because in a try block, exception handlers are checked by their appearance order. If a matched exception type is found in a catch clause, the subsequent catch clauses will be ignored. For example, in the exception handling block below, catch (...) appears firstly and it can catch any exceptions. So other catch clauses will never be checked. Therefore, catch (...) should always be put last.

In the VC++ 6.0 environment, wemust set the following to use the exception handling facility:

  1. Open “Project Settings” dialog;
  2. Choose “C/C++” card;
  3. Choose “C++ Language” in the Category box;
  4. Choose “Enable Exception Handling”.

12.2.2Exception Interface Declaration

To make the program more readable, and to let the user know all possible exceptions that could be thrown by a function, we can list all possible exception types in the function declaration. For example,

indicates that fun() can, and can only, throw exceptions whose types are A, B, C, D, and their subtypes.

If there is no exception interface declaration in the function declaration, it can throw exceptions of any type. For example,

A function that doesn’t throw exceptions of any type can declare as follows:

12.3Destruction and Construction in Exception Handling

The real capacity of exception handling in C++ lies in not only its ability to handle exceptions of various types, but also its ability to call destructors for all local objects constructed before throwing exceptions.

In a program, after a matched catch exception handler is found, if the exception type declaration of the catch clause is a value parameter, its initialization mode is to copy the thrown exception object; if the exception type declaration of the catch clause is a reference, its initialization mode is to make this reference point to the exception object.

After parameters of the exception type declaration are initialized, the unfolding process of the stack will begin. This contains the destruction of all automatic objects constructed (and not destructed yet) from the start of the try block to the place where the exception is thrown. The order of destruction is opposite to that of construction. And the program will resume execution after the last catch handler.

Example 12.2: Use C++ Exception Handling of Classes with Destructors.

The output of the program is as follows.

Note that in this example, the two catch handlers both specify exception parameters (parameters of the catch clause):

In fact, we don’t have to specify these parameters (E and str). In many cases, it is enough to notify that the handler has an exception of a certain type. But we do need to specify parameters when accessing exception objects. Otherwise, we cannot access the object in the catch clause. For example,

We can throw the exception being handled currently again by a throw expression without operands. This expression can only appear in a catch handler or the inner functions of the catch handler. The exception object being thrown again is the original exception object (not a copy). For example,

12.4Exception Handling of Standard Library

The C++ standard provides a standard exception structure that begins with the base class exception. All exception handlings thrown by the standard library are derived from this base class. These classes are composed of derivation and inheritance relationships shown in Figure 12.2. The base class provides a what() service, redefines in each derived class, and sends an error message.

The immediately derived classes runtime_error and logic_error can be derived from the base class exception directly. Each derived class can be used for deriving other classes.

When the global operator new fails, the bad_alloc exception will be thrown. During the execution, when the dynamic type cast operation fails, dynamic_cast will throw a bad_cast exception. In the process of type identification, if the parameter of typeid is a zero or null pointer, the bad_typeId exception will be thrown. When unexpected exceptions happen, the system will call the bad_exception exception thrown by the function unexpected() if std::bad_exception is added in the throw list of the function, and won’t terminate the program or call the function assigned by set_unexpected.

Fig. 12.2: Inheritance relationship between standard exception classes.

Exceptions of the C++ standard library are always derived from the logic_error logic class. It denotes logic errors in the program or violations of the invariance of the class, which can be avoided by writing correct codes. C++ standard library provides the following types of logic errors: invalid_argument denotes passing invalid arguments to functions. Length_error denotes that the length exceeds the maximum allowable length of the operational object. Out_of_range denotes that the value of the array subscript exceeds the defined range. Domain_error denotes invalid preprocessing errors. In addition, the stream class library of the standard library provides a special exception named IOS_base::failure. When the state of the data stream changes because of error or reaching the end of file, it will be thrown.

Exception classes derived from runtime_error are base classes of several other exception classes that denote errors that occur only in the program execution. Range_error denotes the interval error in internal computation, overflow_error denotes overflow errors in computation, and underflow_error denotes underflow error in computation.

To use exception classes, the corresponding header files should be contained. Exception base classes exception and bad_exception are defined in <exception>. Bad_alloc is defined in <new>. Bad_cast and bad_typeid are defined in <typeinfo>. Iso_base::failure is defined in <ios>. Other exception classes are defined in <stdexcept>.

C++ standard library guarantees that exceptions will not cause resource leak and the integrity of containers is maintained.

  1. For base containers implemented by nodes, such as the list container, set container, multiset container, map container, and multimap container, if the construction of nodes fails, they should remain the same. Similarly, we should ensure that the move and remove operations of nodes are successful. When adding multiple elements into the sequence associative container, in order to keep the ordered arrangement of the data, we should ensure that if the insertion fails, the container elements won’t be changed. For the remove operation, we should guarantee the success of the operation. For example, to list a container, all operations besides remove(), remove_if(), merge(), sort(), and unique() should be successful, or they should ensure that the container remains the same.
  2. For containers based on array implementation, such as vectors and deques, if the add operation of elements fails, we must make a backup of all elements after the insert point before the insertion to recover the initial state of the container. This can be time consuming. For functions push() and pop(), since they operate on the tail element of the container, we need not back up any element. Once the exception occurs, these two functions can ensure that the container returns to the original state.

12.5Program Example Improvement to Personal Information Administration Program in a Small Company

As in the previous chapters, we take the personal information management program as an example. When accessing the disk file containing personal information, if the file does not exist, the problem cannot be processed. To handle this error condition, we add the exception handling facility to file access in Example 11.11 in Chapter 11. When accessing a file, if it does not exist, the program will throw an exception and quit. 12_3.cpp and employee.cpp will be connected together after compiling. If we use a VC++ development environment, 12_3.cpp and employee.cpp should be put in the same project.

Example 12.3: Personal Information Management.

Here, the program reads and displays the personal information stored in a disk file. If file access fails, we use the exception handling facility to solve the problem.

If the specified file doesn’t exist, the result is as follows:

If the specified file exists and is correct, the result is:

12.6Summary

Some unavoidable errors during program execution are to be expected. When such errors occur, we should allow the user to correct the environmental errors and allow the program to continue to run. This is the task of exception handlers. C++ language provides internal support for handling exceptions. The try, throw, and catch statements are the mechanism for implementing exception handling in C++.

Tomake the program more readable, and let the user know all possible exceptions that could be thrown by the function, we can list all possible exception types in the function declaration. This is the exception interface declaration.

The real capacity of exception handling in C++ lies in not only its ability to handle exceptions of various types, but also its ability to call destructors for all local objects constructed before the throwing of the exception.

In the last section of the chapter, we introduced the standard exception classes and their functions in the C++ standard library, and outlined a key property that is guaranteed by the exception handling in the C++ standard library. That is, exceptions will not cause resource leak and the integrity of containers is maintained.

Exercises

12.1 What is an exception? What is exception handling?

12.2 What are the advantages of the exception handling facility in C++?

12.3 Explain the use of throw, try, and catch statements by examples.

12.4 Design an exception abstract class Exception, and derive an OutofMemry class to respond to the out of memory situation, and a RangeError class to respond to the exception that input numbers are not in the specified range. Implement and test these classes.

12.5 Practice using try and catch statements. Use new to allocate memory in the program. If the operation fails, use a try statement to trigger an exception of the char type, and catch the exception using a catch statement.

12.6 Define an exception class CException, which has a member function Reason() to display the type of the exception. Define function fn1() to trigger an exception, call fn1() in the try module of the main function, and catch the exception in catch module. Observe the execution flow of the program.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset