Выбрать главу

  size_t someMoreCount =

  sizeof someMoreNums / sizeof someMoreNums[0];

  HasPointers h1("Hello", someNums, someCount);

  HasPointers h2("Goodbye", someMoreNums, someMoreCount);

  cout << h1 << endl;  // Hello: 1 2 3 4

  h1 = h2;

  cout << h1 << endl;  // Goodbye: 5 6 7

} ///:~

For convenience, HasPointers uses the MyData class as a handle to the two pointers. Whenever it’s time to allocate more memory, whether during construction or assignment, the first clone function is ultimately called to do the job. If memory fails for the first call to the new operator, a bad_alloc exception is thrown automatically. If it happens on the second allocation (for theInts), we have to clean up the memory for theString—hence the first try block that catches a bad_alloc exception. The second try block isn’t crucial here because we’re just copying ints and pointers (so no exceptions will occur), but whenever you copy objects, their assignment operators can possibly cause an exception, in which case everything needs to be cleaned up. In both exception handlers, notice that we rethrow the exception. That’s because we’re just managing resources here; the user still needs to know that something went wrong, so we let the exception propagate up the dynamic chain. Software libraries that don’t silently swallow exceptions are called exception neutral. Always strive to write libraries that are both exception safe and exception neutral.[6]

If you inspect the previous code closely, you’ll notice that none of the delete operations will throw an exception. This code actually depends on that fact. Recall that when you call delete on an object, the object’s destructor is called. It turns out to be practically impossible, therefore, to design exception-safe code without assuming that destructors don’t throw exceptions. Don’t let destructors throw exceptions! (We’re going to remind you about this once more before this chapter is done)[7] .

Programming with exceptions

For most programmers, especially C programmers, exceptions are not available in their existing language and take a bit of adjustment. Here are some guidelines for programming with exceptions.

When to avoid exceptions

Exceptions aren’t the answer to all problems. In fact, if you simply go looking for something to pound with your new hammer, you’ll cause trouble. The following sections point out situations in which exceptions are not warranted. Probably the best advice for deciding when to use exceptions is to throw exceptions only when a function fails to meet its specification.

Not for asynchronous events

The Standard C signal( ) system and any similar system handle asynchronous events: events that happen outside the flow of a program, and thus events the program cannot anticipate. You cannot use C++ exceptions to handle asynchronous events because the exception and its handler are on the same call stack. That is, exceptions rely on the dynamic chain of function calls on the program’s runtime stack (dynamic scope, if you will), whereas asynchronous events must be handled by completely separate code that is not part of the normal program flow (typically, interrupt service routines or event loops). Don’t throw exceptions from interrupt handlers.

This is not to say that asynchronous events cannot be associated with exceptions. But the interrupt handler should do its job as quickly as possible and then return. The typical way to handle this situation is to set a flag in the interrupt handler, and check it synchronously in the mainline code.

Not for benign error conditions

If you have enough information to handle an error, it’s not an exception. Take care of it in the current context rather than throwing an exception to a larger context.

Also, C++ exceptions are not thrown for machine-level events such as divide-by-zero.[8] It’s assumed that some other mechanism, such as the operating system or hardware, deals with these events. In this way, C++ exceptions can be reasonably efficient, and their use is isolated to program-level exceptional conditions.

Not for flow-of-control

An exception looks somewhat like an alternate return mechanism and somewhat like a switch statement, so you might be tempted to use an exception instead of these ordinary language mechanisms. This is a bad idea, partly because the exception-handling system is significantly less efficient than normal program execution; exceptions are a rare event, so the normal program shouldn’t pay for them. Also, exceptions from anything other than error conditions are quite confusing to the user of your class or function.

You’re not forced to use exceptions

Some programs are quite simple (small utilities, for example). You might only need to take input and perform some processing. In these programs, you might attempt to allocate memory and fail, try to open a file and fail, and so on. It is acceptable in these programs to display a message and exit the program, allowing the system to clean up the mess, rather than to work hard to catch all exceptions and recover all the resources yourself. Basically, if you don’t need to use exceptions, you don’t have to use them.

New exceptions, old code

Another situation that arises is the modification of an existing program that doesn’t use exceptions. You might introduce a library that does use exceptions and wonder if you need to modify all your code throughout the program. Assuming you have an acceptable error-handling scheme already in place, the most straightforward thing to do is surround the largest block that uses the new library (this might be all the code in main( )) with a try block, followed by a catch(...) and basic error message). You can refine this to whatever degree necessary by adding more specific handlers, but, in any case, the code you’re forced to add can be minimal. It’s even better, of course, to isolate your exception-generating code in a try block and write handlers to convert the exceptions into your existing error-handling scheme.

It’s truly important to think about exceptions when you’re creating a library for someone else to use, especially in situations in which you can’t know how they need to respond to critical error conditions (recall the earlier discussions on exception safety and why there are no exception specifications in the Standard C++ Library).

Typical uses of exceptions

Do use exceptions to do the following:

·         Fix the problem and call the function which caused the exception again.

·         Patch things up and continue without retrying the function.

вернуться

6

If you’re interested in a more in-depth analysis of exception safety issues, the definitive reference is Herb Sutter’s Exceptional C++, Addison-Wesley, 2000.

вернуться

7

The library function uncaught_exception( ) returns true in the middle of stack unwinding, so technically you can test uncaught_exception( ) for false and let an exception escape from a destructor. We’ve never seen a situation in which this constituted good design, however, so we only mention it in this footnote.

вернуться

8

Some compilers do throw exceptions in these cases, but they usually provide a compiler option to disable this (unusual) behavior.