pickzy.com

C  |  C++  |  Objective-C  |  VC++  |  Win32  |  MFC  |  Java  |  Php  |  Delphi  |  Visual Basic  |  .Net  |  Networking  |  General  |  Games  |  Jobs  |  Javascript  |  




Menu

pickSourcecode.com


        

 




 

Cpp > Articles

 

Exception (Runtime Error) Handling - cpp

Exception Handling

An exception is a run-time error. Proper handling of exceptions is an important programming issue. This is because exceptions can and do happen in practice and programs are generally expected to behave gracefully in face of such exceptions. Unless an exception is properly handled, it is likely to result in abnormal program termination and potential loss of work. For example, an undetected division by zero or dereferencing of an invalid pointer will almost certainly terminate the program abruptly.
    Exception handling consists of three things: (i) the detecting of a run-time error, (ii) raising an exception in response to the error, and (ii) taking corrective action. The latter is called recovery. Some exceptions can be fully recovered from so that execution can proceed unaffected. For example, an invalid argument value passed to a function may be handled by substituting a reasonable default value for it. Other exceptions can only be partially handled. For example, exhaustion of the heap memory can be handled by abandoning the current operation and returning to a state where other operations (such as saving the currently open files to avoid losing their contents) can be attempted.
    C++ provides a language facility for the uniform handling of exceptions. Under this scheme, a section of code whose execution may lead to run-time errors is labeled as a try block. Any fragment of code activated during the execution of a try block can raise an exception using a throw clause. All exceptions are typed (i.e., each exception is denoted by an object of a specific type). A try block is followed by one or more catch clauses. Each catch clause is responsible for the handling of exceptions of a particular type.
    When an exception is raised, its type is compared against the catch clauses following it. If a matching clause is found then its handler is executed. Otherwise, the exception is propagated up, to an immediately enclosing try block (if any). The process is repeated until either the exception is handled by a matching catch clause or it is handled by a default handler.


The Throw Clause
An exception is raised by a throw clause, which has the general form

    throw  object;

where object is an object of a built-in or user-defined type. Since an exception is matched by the type of object and not its value, it is customary to define classes for this exact purpose.
    For example, recall the Stack class template discussed in Chapter 9 (see Listing 10.1).

template <class Type>
class Stack {
public:
            Stack    (int max);
            ~Stack    (void)          {delete [] stack;}
    void    Push        (Type &val);
    void    Pop        (void);
    Type&    Top        (void);
    friend    ostream& operator << (ostream&, Stack<Type>);
private:
    Type        *stack;
    int         top;
    const int    maxSize;
};

There are a number of potential run-time errors which may affect the member functions of Stack:
·    The constructor parameter max may be given a nonsensical value. Also, the constructor’s attempt at dynamically allocating storage for stack may fail due to heap exhaustion. We raise exceptions BadSize and HeapFail in response to these:

template <class Type>
Stack<Type>::Stack (int max) : maxSize(max)
{
    if (max <= 0)
        throw BadSize();
    if ((stack = new Type[max]) == 0)
        throw HeapFail();
    top = -1;
}

 An attempt to push onto a full stack results in an overflow. We raise an Overflow exception in response to this:

template <class Type>
void Stack<Type>::Push (Type &val)
{
    if (top+1 < maxSize)
        stack[++top] = val;
    else
        throw Overflow();
}

 An attempt to pop from an empty stack results in an underflow. We raise an Underflow exception in response to this:

template <class Type>
void Stack<Type>::Pop (void)
{
    if (top >= 0)
        --top;
    else
        throw Underflow();
}

Attempting to examine the top element of an empty stack is clearly an error. We raise an Empty exception in response to this:

template <class Type>
Type &Stack<Type>::Top (void)
{
    if (top < 0)
        throw Empty();
    return stack[top];
}

 Suppose that we have defined a class named Error for exception handling purposes. The above exceptions are easily defined as derivations of Error:

class Error         { /* ... */ };
class BadSize   : public Error {};
class HeapFail  : public Error {};
class Overflow  : public Error {};
class Underflow : public Error {};
class Empty        : public Error {};

      
The Try Block and Catch Clauses
A code fragment whose execution may potentially raise exceptions is enclosed by a try block, which has the general form

    try {
        statements
    }

where statements represents one or more semicolon-terminated statements. In other words, a try block is like a compound statement preceded by the try keyword.
    A try block is followed by catch clauses for the exceptions which may be raised during the execution of the block. The role of the catch clauses is to handle the respective exceptions. A catch clause (also called a handler) has the general form

    catch (type  par)    { statements }

where type is the type of the object raised by the matching exception, par is optional and is an identifier bound to the object raised by the exception, and statements  represents zero or more semicolon-terminated statements.
    For example, continuing with our Stack class, we may write:

    try {
        Stack<int> s(3);
        s.Push(10);
        //...
        s.Pop();
        //...
    }
    catch (Underflow)    {cout << "Stack underflow\n";}
    catch (Overflow)        {cout << "Stack overflow\n";}
    catch (HeapFail)        {cout << "Heap exhausted\n";}
    catch (BadSize)        {cout << "Bad stack size\n";}
    catch (Empty)        {cout << "Empty stack\n";}

For simplicity, the catch clauses here do nothing more than outputting a relevant message.
    When an exception is raised by the code within the try block, the catch clauses are examined in the order they appear. The first matching catch clause is selected and its statements are executed. The remaining catch clauses are ignored.
    A catch clause (of type C) matches an exception (of type E) if:
·    C and E are the same type, or
·    One is a reference or constant of the other type, or
·    One is a nonprivate base class of the other type, or
·    Both are pointers and one can be converted to another by implicit type conversion rules.
    Because of the way the catch clauses are evaluated, their order of appearance is significant. Care should be taken to place the types which are likely to mask other types last. For example, the clause type void* will match any pointer and should therefore appear after other pointer type clauses:

try {
    //...
}
catch (char*)    {/*...*/}
catch (Point*)    {/*...*/}
catch (void*)    {/*...*/}

The special catch clause type

catch (...)        { /* ... */ }

will match any exception type and if used, like a default case in a switch statement, should always appear last.
    The statements in a catch clause can also throw exceptions. The case where the matched exception is to be propagated up can be signified by an empty throw:

catch (char*)    {
    //...
    throw;                // propagate up the exception
}

    An exception which is not matched by any catch clause after a try block, is propagated up to an enclosing try block. This process is continued until either the exception is matched or no more enclosing try block remains. The latter causes the predefined function terminate to be called, which simply terminates the program. This function has the following type:

typedef void (*TermFun)(void);

The default terminate function can be overridden by calling set_terminate and passing the replacing function as its argument:

TermFun set_terminate(TermFun);

Set_terminate returns the previous setting.
        ¨
Function Throw Lists
It is a good programming practice to specify what exceptions a function may throw. This enables function users to quickly determine the list of exceptions that their code will have to handle. A function prototype may be appended with a throw list for this purpose:

    type function (parameters) throw (exceptions);

where exceptions denotes a list of zero or more comma-separated exception types which function may directly or indirectly throw. The list is also an assurance that function will not throw any other exceptions.
    For example,

void Encrypt (File &in, File &out, char *key)
     throw (InvalidKey, BadFile, const char*);

specifies that Encrypt may throw an InvalidKey, BadFile, or const char* exception, but none other. An empty throw list specifies that the function will not throw any exceptions:

void Sort (List list) throw ();

    In absence of a throw list, the only way to find the exceptions that a function may throw is to study its code (including other functions that it calls). It is generally expected to at least define throw lists for frequently-used functions.
    Should a function throw an exception which is not specified in its throw list, the predefined function unexpected is called. The default behavior of unexpected is to terminate the program. This can be overridden by calling set_unexpected (which has the same signature as set_terminate) and passing the replacing function as its argument:

TermFun set_unexpected(TermFun);

As before, set_unexpected returns the previous setting.
     

 
Privacy Policy | About Us