C++: The Exception Mechanism - dummies

C++: The Exception Mechanism

By Stephen R. Davis

The next time you are examining a C++ code example, take a closer look at the steps that the code goes through to handle an exception. When the throw occurs, C++ first copies the thrown object to some neutral place. It then begins looking for the end of the current try block.

If a try block is not found in the current function, control passes to the calling function. A search is then made of that function. If no try block is found there, control passes to the function that called it, and so on up the stack of calling functions. This process is called unwinding the stack.

An important feature of stack unwinding is that as each stack is unwound, objects that go out of scope are destructed just as though the function had executed a return statement. This keeps the program from losing assets or leaving objects dangling.

When the encasing try block is found, the code searches the first catch phrase immediately following the closing brace of the catch block. If the object thrown matches the type of argument specified in the catch statement, control passes to that catch phrase.

If not, a check is made of the next catch phrase. If no matching catch phrases are found, the code searches for the next higher level try block in an ever-outward spiral until an appropriate catch can be found. If no catch phrase is found, the program is terminated.

Consider the following example:

// CascadingException - the following program demonstrates
//              an example of stack unwinding
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
// prototypes of some functions that we will need later
void f1();
void f2();
void f3();
class Obj
{
  public:
    Obj(char c) : label(c)
    { cout << "Constructing object " << label << endl;}
    ~Obj()
    { cout << "Destructing object " << label << endl; }
  protected:
    char label;
};
int main(int nNumberofArgs, char* pszArgs[])
{
    f1();
    // wait until user is ready before terminating program
    // to allow the user to see the program results
    cout << "Press Enter to continue..." << endl;
    cin.ignore(10, 'n');
    cin.get();
    return 0;
}
void f1()
{
    Obj a('a');
    try
    {
        Obj b('b');
        f2();
    }
    catch(float f)
    {
        cout << "Float catch" << endl;
    }
    catch(int i)
    {
        cout << "Int catch" << endl;
    }
    catch(...)
    {
        cout << string("Generic catch") << endl;
    }
}
void f2()
{
    try
    {
        Obj c('c');
        f3();
    }
    catch(string msg)
    {
        cout << "String catch" << endl;
    }
}
void f3()
{
    Obj d('d');
    throw 10;
}

The output from executing this program appears as follows:

Constructing object a
Constructing object b
Constructing object c
Constructing object d
Destructing object d
Destructing object c
Destructing object b
Int catch
Destructing object a
Press Enter to continue...

First, you see the four objects a, b, c, and d being constructed as main() calls f1() which calls f2() which calls f3(). Rather than return, however, f3() throws the integer 10. Because no try block is defined in f3(), C++ unwinds f3()′s stack, causing object d to be destructed.

The next function up the chain, f2() defines a try block, but its only catch phrase is designed to handle a string, which doesn′t match the int thrown. Therefore, C++ continues looking. This unwinds f2()′s stack, resulting in object c being destructed.

Back in f1(), C++ finds another try block. Exiting that block causes object b to go out of scope. C++ skips the first catch phrase for a float. The next catch phrase matches the int exactly, so C++ passes control to this phrase.

Control passes from the catch(int) phrase to the closing brace of the final catch phrase and from there back to main(). The final catch(…) phrase, which would catch any object thrown, is skipped because a matching catch phrase was already found.