📄 mc3.htm
字号:
class exception { // as above, this is apublic: // standard exception classvirtual const char * what() throw(); // returns a brief descrip. ... // of the exception (see // Item 14 for info about}; // the "throw()" at the // end of the declaration)class runtime_error: // also from the standard public exception { ... }; // C++ exception hierarchyclass Validation_error: // this is a class added by public runtime_error { // a clientpublic: virtual const char * what() throw(); // this is a redefinition ... // of the function declared}; // in class exception abovevoid someFunction() // may throw a validation{ // exception ... if (a validation test fails) { throw Validation_error(); }...}void doSomething(){ try { someFunction(); // may throw a validation } // exception catch (exception ex) { // catches all exceptions // in or derived from // the standard hierarchy cerr << ex.what(); // calls exception::what(), ... // never } // Validation_error::what()}The version of what that is called is that of the base class, even though the thrown exception is of type Validation_error and Validation_error redefines that virtual function. This kind of slicing behavior is almost never what you want.That leaves only catch-by-reference. Catch-by-reference suffers from none of the problems we have discussed. Unlike catch-by-pointer, the question of object deletion fails to arise, and there is no difficulty in catching the standard exception types. Unlike catch-by-value, there is no slicing problem, and exception objects are copied only once.If we rewrite the last example using catch-by-reference, it looks like this: void someFunction() // nothing changes in this{ // function ... if (a validation test fails) { throw Validation_error(); } ...}void doSomething(){ try { someFunction(); // no change here } catch (exception& ex) { // here we catch by reference // instead of by value cerr << ex.what(); // now calls // Validation_error::what(), ... // not exception::what() }}There is no change at the throw site, and the only change in the catch clause is the addition of an ampersand. This tiny modification makes a big difference, however, because virtual functions in the catch block now work as we expect: functions in Validation_error are invoked if they redefine those in exception.What a happy confluence of events! If you catch by reference, you sidestep questions about object deletion that leave you damned if you do and damned if you don't; you avoid slicing exception objects; you retain the ability to catch standard exceptions; and you limit the number of times exception objects need to be copied. So what are you waiting for? Catch exceptions by reference! Back to Item 13: Catch exceptions by referenceContinue to Item 15: Understand the costs of exception handlingItem 14: Use exception specifications judiciously.There's no denying it: exception specifications have appeal. They make code easier to understand, because they explicitly state what exceptions a function may throw. But they're more than just fancy comments. Compilers are sometimes able to detect inconsistent exception specifications during compilation. Furthermore, if a function throws an exception not listed in its exception specification, that fault is detected at runtime, and the special function unexpected is automatically invoked. Both as a documentation aid and as an enforcement mechanism for constraints on exception usage, then, exception specifications seem attractive.As is often the case, however, beauty is only skin deep. The default behavior for unexpected is to call terminate, and the default behavior for terminate is to call abort, so the default behavior for a program with a violated exception specification is to halt. Local variables in active stack frames are not destroyed, because abort shuts down program execution without performing such cleanup. A violated exception specification is therefore a cataclysmic thing, something that should almost never happen.Unfortunately, it's easy to write functions that make this terrible thing occur. Compilers only partially check exception usage for consistency with exception specifications. What they do not check for what the language standard prohibits them from rejecting (though they may issue a warning) is a call to a function that might violate the exception specification of the function making the call.Consider a declaration for a function f1 that has no exception specification. Such a function may throw any kind of exception: extern void f1(); // might throw anythingNow consider a function f2 that claims, through its exception specification, it will throw only exceptions of type int: void f2() throw(int);It is perfectly legal C++ for f2 to call f1, even though f1 might throw an exception that would violate f2's exception specification: void f2() throw(int){ ... f1(); // legal even though f1 might throw // something besides an int ...}This kind of flexibility is essential if new code with exception specifications is to be integrated with older code lacking such specifications.Because your compilers are content to let you call functions whose exception specifications are inconsistent with those of the routine containing the calls, and because such calls might result in your program's execution being terminated, it's important to write your software in such a way that these kinds of inconsistencies are minimized. A good way to start is to avoid putting exception specifications on templates that take type arguments. Consider this template, which certainly looks as if it couldn't throw any exceptions: // a poorly designed template wrt exception specificationstemplate<class T>bool operator==(const T& lhs, const T& rhs) throw(){ return &lhs == &rhs;}This template defines an operator== function for all types. For any pair of objects of the same type, it returns true if the objects have the same address, otherwise it returns false.This template contains an exception specification stating that the functions generated from the template will throw no exceptions. But that's not necessarily true, because it's possible that operator& (the address-of operator see Item E45) has been overloaded for some types. If it has, operator& may throw an exception when called from inside operator==. If it does, our exception specification is violated, and off to unexpected we go.This is a specific example of a more general problem, namely, that there is no way to know anything about the exceptions thrown by a template's type parameters. We can almost never provide a meaningful exception specification for a template, because templates almost invariably use their type parameter in some way. The conclusion? Templates and exception specifications don't mix.A second technique you can use to avoid calls to unexpected is to omit exception specifications on functions making calls to functions that themselves lack exception specifications. This is simple common sense, but there is one case that is easy to forget. That's when allowing users to register callback functions: // Function pointer type for a window system callback// when a window system event occurstypedef void (*CallBackPtr)(int eventXLocation, int eventYLocation, void *dataToPassBack);// Window system class for holding onto callback// functions registered by window system clientsclass CallBack {public: CallBack(CallBackPtr fPtr, void *dataToPassBack) : func(fPtr), data(dataToPassBack) {} void makeCallBack(int eventXLocation, int eventYLocation) const throw();private: CallBackPtr func; // function to call when // callback is made void *data; // data to pass to callback}; // function// To implement the callback, we call the registered func-// tion with event's coordinates and the registered datavoid CallBack::makeCallBack(int eventXLocation, int eventYLocation) const throw(){ func(eventXLocation, eventYLocation, data);}Here the call to func in makeCallBack runs the risk of a violated exception specification, because there is no way of knowing what exceptions func might throw.This problem can be eliminated by tightening the exception specification in the CallBackPtr typedef:5 typedef void (*CallBackPtr)(int eventXLocation, int eventYLocation, void *dataToPassBack) throw();Given this typedef, it is now an error to register a callback function that fails to guarantee it throws nothing: // a callback function without an exception specificationvoid callBackFcn1(int eventXLocation, int eventYLocation, void *dataToPassBack);void *callBackData;...CallBack c1(callBackFcn1, callBackData); // error! callBackFcn1 // might throw an exception// a callback function with an exception specificationvoid callBackFcn2(int eventXLocation, int eventYLocation, void *dataToPassBack) throw();CallBack c2(callBackFcn2, callBackData); // okay, callBackFcn2 has a // conforming ex. spec.This checking of exception specifications when passing function pointers is a relatively recent addition to the language, so don't be surprised if your compilers don't yet support it. If they don't, it's up to you to ensure you don't make this kind of mistake.A third technique you can use to avoid calls to unexpected is to handle exceptions "the system" may throw. Of these exceptions, the most common is bad_alloc, which is thrown by operator new and operator new[] when a memory allocation fails (see Item 8). If you use the new operator (again, see Item 8) in any function, you must be prepared for the possibility that the function will encounter a bad_alloc exception.Now, an
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -