📄 mi12.htm
字号:
More Effective C++ | Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function Back to Item 11: Prevent exceptions from leaving destructorsContinue to Item 13: Catch exceptions by referenceItem 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function.The syntax for declaring function parameters is almost the same as that for catch clauses: class Widget { ... }; // some class; it makes no // difference what it isvoid f1(Widget w); // all these functionsvoid f2(Widget& w); // take parameters ofvoid f3(const Widget& w); // type Widget, Widget&, orvoid f4(Widget *pw); // Widget*void f5(const Widget *pw);catch (Widget w) ... // all these catch clausescatch (Widget& w) ... // catch exceptions ofcatch (const Widget& w) ... // type Widget, Widget&, orcatch (Widget *pw) ... // Widget*catch (const Widget *pw) ...You might therefore assume that passing an exception from a throw site to a catch clause is basically the same as passing an argument from a function call site to the function's parameter. There are some similarities, to be sure, but there are significant differences, too.Let us begin with a similarity. You can pass both function parameters and exceptions by value, by reference, or by pointer. What happens when you pass parameters and exceptions, however, is quite different. This difference grows out of the fact that when you call a function, control eventually returns to the call site (unless the function fails to return), but when you throw an exception, control does not return to the throw site.Consider a function that both passes a Widget as a parameter and throws a Widget as an exception: // function to read the value of a Widget from a streamistream operator>>(istream& s, Widget& w);void passAndThrowWidget(){ Widget localWidget; cin >> localWidget; // pass localWidget to operator>> throw localWidget; // throw localWidget as an exception}When localWidget is passed to operator>>, no copying is performed. Instead, the reference w inside operator>> is bound to localWidget, and anything done to w is really done to localWidget. It's a different story when localWidget is thrown as an exception. Regardless of whether the exception is caught by value or by reference (it can't be caught by pointer that would be a type mismatch), a copy of localWidget will be made, and it is the copy that is passed to the catch clause. This must be the case, because localWidget will go out of scope once control leaves passAndThrowWidget, and when localWidget goes out of scope, its destructor will be called. If localWidget itself were passed to a catch clause, the clause would receive a destructed Widget, an ex-Widget, a former Widget, the carcass of what once was but is no longer a Widget. That would not be useful, and that's why C++ specifies that an object thrown as an exception is always copied.This copying occurs even if the object being thrown is not in danger of being destroyed. For example, if passAndThrowWidget declares localWidget to be static, void passAndThrowWidget(){ static Widget localWidget; // this is now static; it // will exist until the // end of the program cin >> localWidget; // this works as before throw localWidget; // a copy of localWidget is} // still made and throwna copy of localWidget would still be made when the exception was thrown. This means that even if the exception is caught by reference, it is not possible for the catch block to modify localWidget; it can only modify a copy of localWidget. This mandatory copying of exception objects helps explain another difference between parameter passing and throwing an exception: the latter is typically much slower than the former (see Item 15).When an object is copied for use as an exception, the copying is performed by the object's copy constructor. This copy constructor is the one in the class corresponding to the object's static type, not its dynamic type. For example, consider this slightly modified version of passAndThrowWidget: class Widget { ... };class SpecialWidget: public Widget { ... };void passAndThrowWidget(){ SpecialWidget localSpecialWidget; ... Widget& rw = localSpecialWidget; // rw refers to a // SpecialWidget throw rw; // this throws an // exception of type} // Widget!Here a Widget exception is thrown, even though rw refers to a SpecialWidget. That's because rw's static type is Widget, not SpecialWidget. That rw actually refers to a SpecialWidget is of no concern to your compilers; all they care about is rw's static type. This behavior may not be what you want, but it's consistent with all other cases in which C++ copies objects. Copying is always based on an object's static type (but see Item 25 for a technique that lets you make copies on the basis of an object's dynamic type).The fact that exceptions are copies of other objects has an impact on how you propagate exceptions from a catch block. Consider these two catch blocks, which at first glance appear to do the same thing: catch (Widget& w) // catch Widget exceptions{ ... // handle the exception throw; // rethrow the exception so it} // continues to propagatecatch (Widget& w) // catch Widget exceptions{ ... // handle the exception throw w; // propagate a copy of the} // caught exceptionThe only difference between these blocks is that the first one rethrows the current exception, while the second one throws a new copy of the current exception. Setting aside the performance cost of the additional copy operation, is there a difference between these approaches?There is. The first block rethrows the current exception, regardless of its type. In particular, if the exception originally thrown was of type SpecialWidget, the first block would propagate a SpecialWidget exception, even though w's static type is Widget. This is because no copy is made when the exception is rethrown. The second catch block throws a new exception, which will always be of type Widget, because that's w's static type. In general, you'll want to use the throw;syntax to rethrow the current exception, because there's no chance that that will change the type of the exception being propagated. Furthermore, it's more efficient, because there's no need to generate a new exception object.(Incidentally, the copy made for an exception is a temporary object. As Item 19 explains, this gives compilers the right to optimize it out of existence. I wouldn't expect your compilers to work that hard, however. Exceptions are supposed to be rare, so it makes little sense for compiler vendors to pour a lot of energy into their optimization.)Let us examine the three kinds of catch clauses that could catch the Widget exception thrown by passAndThrowWidget. They are:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -