Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ For Dummies (2004) [eng].pdf
Скачиваний:
84
Добавлен:
16.08.2013
Размер:
8.09 Mб
Скачать

Chapter 25: Handling Errors — Exceptions 331

Justifying a New Error Mechanism?

What’s wrong with error returns like FORTRAN used to make? Factorials cannot be negative, so I could have said something like “Okay, if factorial() detects an error, it returns a negative number. The actual value indicates the source of the problem.” What’s wrong with that? That’s how it’s been accomplished for ages.

Unfortunately, several problems arise. First, although it’s true that the result of a factorial can’t be negative, other functions aren’t so lucky. For example, you can’t take the log of a negative number either, but the negative return value trick won’t work here — logarithms can be either negative or positive.

Second, there’s just so much information that you can store in an integer. Maybe you can have –1 for “argument is negative” and –2 for “argument is too large.” But, if the argument is too large, you want to know what the argument is, because that information might help you debug the problem. There’s no place to store that type of information.

Third, the processing of error returns is optional. Suppose someone writes factorial() so that it dutifully checks the argument and returns a negative number if the argument is out of range. If a function that calls factorial() doesn’t check the error return, returning an error value doesn’t do any good. Sure, you can make all kinds of menacing threats, such as “You will check your error returns or else,” and the programmer may have the best of inten­ tions, but you all know that people get lazy and return to their old, non-error- checking ways.

Even if you do check the error return from factorial() or any other func­ tion, what can the function do with the error? It can probably do nothing more than output an error message of your own and return another error indication to the caller, which probably does the same. Pretty soon, all code begins to have the following appearance:

//call some function, check the error return, handle it,

//and return

errRtn = someFunc(); if (errRtn)

{

errorOut(“Error on call to someFunc()”); return MY_ERROR_1;

}

errRtn = someOtherFunc(); if (errRtn)

{

errorOut(“Error on call to someOtherFunc()”); return MY_ERROR_1;

}

332 Part V: Optional Features

This mechanism has several problems:

It’s highly repetitive.

It forces the user to invent and keep track of numerous error return indications.

It mixes the error-handling code into the normal code flow, thereby obscuring the normal, non-error path.

These problems don’t seem so bad in this simple example, but they become increasingly worse as the calling code becomes more complex. The result is that error-handling code doesn’t get written to handle all the conditions that it should.

The exception mechanism addresses these problems by removing the error path from the normal code path. Furthermore, exceptions make error han­ dling obligatory. If your function doesn’t handle the thrown exception, control passes up the chain of called functions until C++ finds a function to handle the error. This also gives you the flexibility to ignore errors that you can’t do any­ thing about anyway. Only the functions that can actually correct the problem need to catch the exception.

Examining the Exception Mechanism

Take a closer look at the steps that the code goes through to handle an excep­ tion. When the throw occurs, C++ first copies the thrown object to some neu­ tral 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.

Chapter 25: Handling Errors — Exceptions 333

Consider the following example:

// CascadingException - note that the following program

//

may generate warnings because the

//

variables f, i and pMsg

//

are not used for anything - the

//

compiler is trying to give you a

//

hint that maybe you don’t

//

need the arguments at all

#include <cstdio>

 

#include <cstdlib>

 

#include <iostream>

 

using namespace std;

 

class Obj

 

{

 

public:

 

Obj(char c)

 

{

 

label = c;

cout << “Constructing object “ << label << endl;

}

~Obj()

{

cout << “Destructing object “ << label << endl;

}

protected: char label;

};

void f1(); void f2(); int f3()

{

Obj a(‘a’); try

{

Obj b(‘b’); f1();

}

catch(float f)

{

cout << “Float catch” << endl;

}

catch(int i)

{

cout << “Int catch” << endl;

}

catch(...)

{

cout << string(“Generic catch”) << endl;

}

}

334 Part V: Optional Features

int main(int nNumberofArgs, char* pszArgs[])

{

f3();

//wait until user is ready before terminating program

//to allow the user to see the program results system(“PAUSE”);

return 0;

}

void f1()

{

try

{

Obj c(‘c’); f2();

}

catch(string msg)

{

cout << “String catch” << endl;

}

}

void f2()

{

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 any key to continue . . .

First, you see the four objects a, b, c, and d being constructed as control passes through each declaration before f2() throws the int 10. Because no try block is defined in f2(), C++ unwinds f2()’s stack, causing object d to be destructed. f1() defines a try block, but its only catch phrase is designed to handle char*, which doesn’t match the int thrown. Therefore, C++ continues looking. This unwinds f1()’s stack, resulting in object c being destructed.

Back in f3(), C++ finds another try block. Exiting that block causes object b to go out of scope. The first catch phrase is designed to catch floats that don’t