B. Stroustrup - The C++ Programming Language (794319), страница 77
Текст из файла (страница 77)
If do_task() can do that job and return a correct result,all is fine. Otherwise, do_task() must report a failure by throwing some exception. The taskmaster()is prepared to handle a Some_error, but some other kind of exception may be thrown. For example,do_task() may call other functions to do a lot of subtasks, and one of those may throw because itcan’t do its assigned subtask. An exception different from Some_error indicates a failure oftaskmaster() to do its job and must be handled by whatever code invoked taskmaster().A called function cannot just return with an indication that an error happened.
If the program isto continue working (and not just print an error message and terminate), the returning function mustleave the program in a good state and not leak any resources. The exception-handling mechanismis integrated with the constructor/destructor mechanisms and the concurrency mechanisms to helpensure that (§5.2). The exception-handling mechanism:• Is an alternative to the traditional techniques when they are insufficient, inelegant, or errorprone• Is complete; it can be used to handle all errors detected by ordinary code• Allows the programmer to explicitly separate error-handling code from ‘‘ordinary code,’’thus making the program more readable and more amenable to tools• Supports a more regular style of error handling, thus simplifying cooperation between separately written program fragmentsAn exception is an object thrown to represent the occurrence of an error.
It can be of any type thatcan be copied, but it is strongly recommended to use only user-defined types specifically definedfor that purpose. That way, we minimize the chances of two unrelated libraries using the samevalue, say 17, to represent different errors, thereby throwing our recovery code into chaos.An exception is caught by code that has expressed interest in handling a particular type ofexception (a catch-clause). Thus, the simplest way of defining an exception is to define a classspecifically for a kind of error and throw that. For example:struct Range_error {};void f(int n){if (n<0 || max<n) throw Range_error {};// ...}If that gets tedious, the standard library defines a small hierarchy of exception classes (§13.5.2).An exception can carry information about the error it represents.
Its type represents the kind oferror, and whatever data it holds represents the particular occurrence of that error. For example, thestandard-library exceptions contain a string value, which can be used to transmit information suchas the location of the throw (§13.5.2).13.1.2 Traditional Error HandlingConsider the alternatives to exceptions for a function detecting a problem that cannot be handledlocally (e.g., an out-of-range access) so that an error must be reported to a caller. Each conventional approach has problems, and none are general:346•Exception HandlingChapter 13Terminate the program. This is a pretty drastic approach.
For example:if (something_wrong) exit(1);•For most errors, we can and must do better. For example, in most situations we should atleast write out a decent error message or log the error before terminating. In particular, alibrary that doesn’t know about the purpose and general strategy of the program in which itis embedded cannot simply exit() or abort().
A library that unconditionally terminates cannotbe used in a program that cannot afford to crash.Return an error value. This is not always feasible because there is often no acceptable‘‘error value.’’ For example:int get_int();•// get next integer from inputFor this input function, every int is a possible result, so there can be no integer value representing an input failure. At a minimum, we would have to modify get_int() to return a pairof values.
Even where this approach is feasible, it is often inconvenient because every callmust be checked for the error value. This can easily double the size of a program (§13.1.7).Also, callers often ignore the possibility of errors or simply forget to test a return value.Consequently, this approach is rarely used systematically enough to detect all errors. Forexample, printf() (§43.3) returns a negative value if an output or encoding error occurred, butprogrammers essentially never test for that. Finally, some operations simply do not havereturn values; a constructor is the obvious example.Return a legal value and leave the program in an ‘‘error state.’’ This has the problem thatthe calling function may not notice that the program has been put in an error state.
Forexample, many standard C library functions set the nonlocal variable errno to indicate anerror (§43.4, §40.3):double d = sqrt(−1.0);•Here, the value of d is meaningless and errno is set to indicate that −1.0 isn’t an acceptableargument for a floating-point square root function. However, programs typically fail to setand test errno and similar nonlocal state consistently enough to avoid consequential errorscaused by values returned from failed calls. Furthermore, the use of nonlocal variables forrecording error conditions doesn’t work well in the presence of concurrency.Call an error-handler function. For example:if (something_wrong) something_handler(); // and possibly continue hereThis must be some other approach in disguise because the problem immediately becomes‘‘What does the error-handling function do?’’ Unless the error-handling function can completely resolve the problem, the error-handling function must in turn either terminate theprogram, return with some indication that an error had occurred, set an error state, or throwan exception.
Also, if the error-handling function can handle the problem without botheringthe ultimate caller, why do we consider it an error?Traditionally, an unsystematic combination of these approached co-exists in a program.Section 13.1.3Muddling Through34713.1.3 Muddling ThroughOne aspect of the exception-handling scheme that will appear novel to some programmers is thatthe ultimate response to an unhandled error (an uncaught exception) is to terminate the program.The traditional response has been to muddle through and hope for the best. Thus, exception handling makes programs more ‘‘brittle’’ in the sense that more care and effort must be taken to get aprogram to run acceptably. This is preferable, though, to getting wrong results later in the development process – or after the development process is considered complete and the program is handedover to innocent users.
Where termination is unacceptable, we can catch all exceptions (§13.5.2.2).Thus, an exception terminates a program only if a programmer allows it to terminate. Typically,this is preferable to the unconditional termination that happens when a traditional incompleterecovery leads to a catastrophic error. Where termination is an acceptable response, an uncaughtexception will achieve that because it turns into a call of terminate() (§13.5.2.5). Also, a noexceptspecifier (§13.5.1.1) can make that desire explicit.Sometimes, people try to alleviate the unattractive aspects of ‘‘muddling through’’ by writingout error messages, putting up dialog boxes asking the user for help, etc.
Such approaches are primarily useful in debugging situations in which the user is a programmer familiar with the structureof the program. In the hands of nondevelopers, a library that asks the (possibly absent) user/operator for help is unacceptable. A good library doesn’t ‘‘blabber’’ in this way. If a user has to beinformed, an exception handler can compose a suitable message (e.g., in Finnish for Finnish usersor in XML for an error-logging system). Exceptions provide a way for code that detects a problemfrom which it cannot recover to pass the problem on to a part of the system that might be able torecover.
Only a part of the system that has some idea of the context in which the program runs hasany chance of composing a meaningful error message.Please recognize that error handling will remain a difficult task and that the exception-handlingmechanism – although more formalized than the techniques it replaces – is still relatively unstructured compared with language features involving only local control flow. The C++ exception-handling mechanism provides the programmer with a way of handling errors where they are most naturally handled, given the structure of a system.
Exceptions make the complexity of error handlingvisible. However, exceptions are not the cause of that complexity. Be careful not to blame themessenger for bad news.13.1.4 Alternative Views of Exceptions‘‘Exception’’ is one of those words that means different things to different people.
The C++ exception-handling mechanism is designed to support handling of errors that cannot be handled locally(‘‘exceptional conditions’’). In particular, it is intended to support error handling in programs composed of independently developed components. Given that there is nothing particularly exceptionalabout a part of a program being unable to perform its given task, the word ‘‘exception’’ may beconsidered a bit misleading. Can an event that happens most times a program is run be consideredexceptional? Can an event that is planned for and handled be considered an error? The answer toboth questions is ‘‘yes.’’ ‘‘Exceptional’’ does not mean ‘‘almost never happens’’ or ‘‘disastrous.’’348Exception HandlingChapter 1313.1.4.1 Asynchronous EventsThe mechanism is designed to handle only synchronous exceptions, such as array range checks andI/O errors. Asynchronous events, such as keyboard interrupts and power failures, are not necessarily exceptional and are not handled directly by this mechanism.