为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

C++中理解“传递参数”和异常之间的差异

2012-09-22 6页 pdf 116KB 12阅读

用户头像

is_117578

暂无简介

举报
C++中理解“传递参数”和异常之间的差异 C++中理解“传递参数”和异常之间的差异 从语法上看,在函数里声明参数与在catch 子句中声明参数几乎没有什么差别: class Widget { ... }; //一个类,具体是什么类 // 在这里并不重要 void f1(Widget w); // 一些函数,其参数分别为 void f2(Widget& w); // Widget, Widget&,或 void f3(const Widget& w); // Widget* 类型 void f4(Widget *pw); void f5(const W...
C++中理解“传递参数”和异常之间的差异
C++中理解“传递参数”和异常之间的差异 从语法上看,在函数里声明参数与在catch 子句中声明参数几乎没有什么差别: class Widget { ... }; //一个类,具体是什么类 // 在这里并不重要 void f1(Widget w); // 一些函数,其参数分别为 void f2(Widget& w); // Widget, Widget&,或 void f3(const Widget& w); // Widget* 类型 void f4(Widget *pw); void f5(const Widget *pw); catch (Widget w) ... //一些 catch 子句,用来 catch (Widget& w) ... //捕获异常,异常的类型为 catch (const Widget& w) ... // Widget, Widget&, 或 catch (Widget *pw) ... // Widget* catch (const Widget *pw) ... 你因此可能会认为用throw抛出一个异常到catch子句中与通过函数调用传递一个 参数两者基本相同。这里面确有一些相同点,但是他们也存在着巨大的差异。 让我们先从相同点谈起。你传递函数参数与异常的途径可以是传值、传递引用或传 递指针,这是相同的。但是当你传递参数和异常时,系统所要完成的操作过程则是完全不同 的。产生这个差异的原因是:你调用函数时,程序的控制权最终还会返回到函数的调用处, 但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。 有这样一个函数,参数类型是Widget,并抛出一个 Widget类型的异常: // 一个函数,从流中读值到Widget 中 istream operator>>(istream& s, Widget& w); void passAndThrowWidget() { Widget localWidget; cin >> localWidget; //传递 localWidget 到 operator>> throw localWidget; // 抛出 localWidget 异常 } 当传递localWidget 到函数operator>>里,不用进行拷贝操作,而是把 operator>> 内的引用类型变量w 指向localWidget,任何对 w的操作实际上都施加到 localWidget上。 这与抛出localWidget 异常有很大不同。不论通过传值捕获异常还是通过引用捕获(不能通 过指针捕获这个异常,因为类型不匹配)都将进行lcalWidget 的拷贝操作,也就说传递到 catch子句中的是 localWidget的拷贝。必须这么做,因为当localWidget 离开了生存空间 后,其析构函数将被调用。如果把localWidget 本身(而不是它的拷贝)传递给catch 子句, 这个子句接收到的只是一个被析构了的Widget,一个Widget 的“尸体”。这是无法使用的。 因此C++规范要求被做为异常抛出的对象必须被复制。 即使被抛出的对象不会被释放,也会进行拷贝操作。例如如果 passAndThrowWidget 函数声明localWidget 为静态变量(static), void passAndThrowWidget() { static Widget localWidget; // 现在是静态变量(static); //一直存在至程序结束 cin >> localWidget; // 象以前那样运行 throw localWidget; // 仍将对localWidget } //进行拷贝操作 当抛出异常时仍将复制出localWidget 的一个拷贝。这表示即使通过引用来捕获异 常,也不能在catch 块中修改localWidget;仅仅能修改 localWidget的拷贝。对异常对象 进行强制复制拷贝,这个限制有助于我们理解参数传递与抛出异常的第二个差异:抛出异常 运行速度比参数传递要慢。 当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数 是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型 (dynamic type)对应类的拷贝构造函数。比如以下这经过少许修改的 passAndThrowWidget: class Widget { ... }; class SpecialWidget: public Widget { ... }; void passAndThrowWidget() { SpecialWidget localSpecialWidget; ... Widget& rw = localSpecialWidget; // rw 引用 SpecialWidget throw rw; //它抛出一个类型为Widget // 的异常 } 这里抛出的异常对象是Widget,即使 rw引用的是一个 SpecialWidget。因为rw 的静态类型(static type)是 Widget,而不是 SpecialWidget。你的编译器根本没有主要到 rw引用的是一个 SpecialWidget。编译器所注意的是rw 的静态类型(static type)。这种行 为可能与你所期待的不一样,但是这与在其他情况下C++中拷贝构造函数的行为是一致的。 异常是其它对象的拷贝,这个事实影响到你如何在catch 块中再抛出一个异常。比 如下面这两个catch 块,乍一看好像一样: catch (Widget& w) // 捕获 Widget 异常 { ... // 处理异常 throw; // 重新抛出异常,让它 } // 继续传递 catch (Widget& w) // 捕获 Widget 异常 { ... // 处理异常 throw w; // 传递被捕获异常的 } // 拷贝 这两个catch 块的差别在于第一个catch 块中重新抛出的是当前捕获的异常,而第 二个catch 块中重新抛出的是当前捕获异常的一个新的拷贝。如果忽略生成额外拷贝的系统 开销,这两种方法还有差异么? 当然有。第一个块中重新抛出的是当前异常(current exception),无论它是什么类 型。特别是如果这个异常开始就是做为SpecialWidget 类型抛出的,那么第一个块中传递出 去的还是SpecialWidget 异常,即使w 的静态类型(static type)是 Widget。这是因为重新 抛出异常时没有进行拷贝操作。第二个catch 块重新抛出的是新异常,类型总是Widget, 因为w 的静态类型(static type)是 Widget。一般来说,你应该用throw 来重新抛出当前的 异常,因为这样不会改变被传递出去的异常类型,而且更有效率,因为不用生成一个新拷贝。 (顺便说一句,异常生成的拷贝是一个临时对象。正如条款19解释的,临时对象能 让编译器优化它的生存期(optimize it out of existence),不过我想你的编译器很难这么 做,因为程序中很少发生异常,所以编译器厂商不会在这方面花大量的精力。) 让我们测试一下下面这三种用来捕获Widget 异常的catch 子句,异常是做为 passAndThrowWidgetp抛出的: catch (Widget w) ... // 通过传值捕获异常 catch (Widget& w) ... // 通过传递引用捕获 // 异常 catch (const Widget& w) ... //通过传递指向const 的引用 //捕获异常 我们立刻注意到了传递参数与传递异常的另一个差异。一个被异常抛出的对象(刚 才解释过,总是一个临时对象)可以通过普通的引用捕获;它不需要通过指向const 对象的引 用(reference-to-const)捕获。在函数调用中不允许转递一个临时对象到一个非const 引用 类型的参数里,但是在异常中却被允许。 让我们先不管这个差异,回到异常对象拷贝的测试上来。我们知道当用传值的方式 传递函数的参数,我们制造了被传递对象的一个拷贝(参见Effective C++ 条款22),并把 这个拷贝存储到函数的参数里。同样我们通过传值的方式传递一个异常时,也是这么做的。 当我们这样声明一个catch 子句时: catch (Widget w) ... // 通过传值捕获 会建立两个被抛出对象的拷贝,一个是所有异常都必须建立的临时对象,第二个是 把临时对象拷贝进w 中。同样,当我们通过引用捕获异常时, catch (Widget& w) ... // 通过引用捕获 catch (const Widget& w) ... //也通过引用捕获 这仍旧会建立一个被抛出对象的拷贝:拷贝是一个临时对象。相反当我们通过引用 传递函数参数时,没有进行对象拷贝。当抛出一个异常时,系统构造的(以后会析构掉)被抛 出对象的拷贝数比以相同对象做为参数传递给函数时构造的拷贝数要多一个。 我们还没有讨论通过指针抛出异常的情况,不过通过指针抛出异常与通过指针传递 参数是相同的。不论哪种方法都是一个指针的拷贝被传递。你不能认为抛出的指针是一个指 向局部对象的指针,因为当异常离开局部变量的生存空间时,该局部变量已经被释放。Catch 子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免。 对象从函数的调用处传递到函数参数里与从异常抛出点传递到catch 子句里所采 用的方法不同,这只是参数传递与异常传递的区别的一个方面,第二个差异是在函数调用者 或抛出异常者与被调用者或异常捕获者之间的类型匹配的过程不同。比如在标准数学库(the standard math library)中 sqrt 函数: double sqrt(double); // from or 我们能这样计算一个整数的平方根,如下所示: int i; double sqrtOfi = sqrt(i); 毫无疑问,C++允许进行从int 到 double 的隐式类型转换,所以在sqrt 的调用中, i 被悄悄地转变为double 类型,并且其返回值也是double。(有关隐式类型转换的详细讨 论参见条款5)一般来说,catch子句匹配异常类型时不会进行这样的转换。见下面的代码: void f(int value) { try { if (someFunction()) { // 如果 someFunction()返回 throw value; //真,抛出一个整形值 ... } } catch (double d) { // 只处理double 类型的异常 ... } ... } 在 try 块中抛出的int 异常不会被处理double 异常的catch 子句捕获。该子句只 能捕获真真正正为double 类型的异常;不进行类型转换。因此如果要想捕获int 异常,必须 使用带有int 或 int&参数的 catch子句。 不过在catch 子句中进行异常匹配时可以进行两种类型转换。第一种是继承类与基 类间的转换。一个用来捕获基类的catch 子句也可以处理派生类类型的异常。例如在标准 C++库(STL)定义的异常类层次中的诊断部分(diagnostics portion )(参见 Effective C++ 条款49)。 捕获runtime_errors 异常的Catch 子句可以捕获range_error 类型和 overflow_error类型的异常,可以接收根类 exception异常的 catch子句能捕获其任意派 生类异常。 这种派生类与基类(inheritance_based)间的异常类型转换可以作用于数值、引用 以及指针上: catch (runtime_error) ... // can catch errors of type catch (runtime_error&) ... // runtime_error, catch (const runtime_error&) ... // range_error, or // overflow_error catch (runtime_error*) ... // can catch errors of type catch (const runtime_error*) ... // runtime_error*, // range_error*, or // overflow_error* 第二种是允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的 catch子句能捕获任何类型的指针类型异常: catch (const void*) ... //捕获任何指针类型异常 传递参数和传递异常间最后一点差别是catch 子句匹配顺序总是取决于它们在程 序中出现的顺序。因此一个派生类异常可能被处理其基类异常的catch 子句捕获,即使同时 存在有能处理该派生类异常的catch 子句,与相同的try 块相对应。例如:
/
本文档为【C++中理解“传递参数”和异常之间的差异】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索