当前位置:天才代写 > tutorial > C语言/C++ 教程 > C++中领略“通报参数”和异常之间的差别

C++中领略“通报参数”和异常之间的差别

2017-11-06 08:00 星期一 所属: C语言/C++ 教程 浏览:482

副标题#e#

从语法上看,在函数里声明参数与在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++类型要求被做为异常抛出的工具必需被复制。


#p#副标题#e#

纵然被抛出的工具不会被释放,也会举办拷贝操纵。譬喻假如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++中拷贝结构函数的行为是一致的。

#p#副标题#e#

异常是其它工具的拷贝,这个事实影响到你如安在catch块中再抛出一个异常。好比下面这两个catch块,乍一看仿佛一样:

catch (Widget& w) // 捕捉Widget异常
{
 ... // 处理惩罚异常
 throw; // 从头抛出异常,让它
} // 继承通报
catch (Widget& w) // 捕捉Widget异常
{
 ... // 处理惩罚异常
 throw w; // 通报被捕捉异常的
} // 拷贝

这两个catch块的不同在于第一个catch块中从头抛出的是当前捕捉的异常,而第二个catch块中从头抛出的是当前捕捉异常的一个新的拷贝。假如忽略生成特别拷贝的系统开销,这两种要领尚有差别么?

#p#分页标题#e#

虽然有。第一个块中从头抛出的是当前异常(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引用范例的参数里,可是在异常中却被答允。

#p#副标题#e#

让我们先不管这个差别,回到异常工具拷贝的测试上来。我们知道当用传值的方法通报函数的参数,我们制造了被通报工具的一个拷贝(拜见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

我们能这样计较一个整数的平方根,如下所示:

#p#副标题#e# 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子句能捕捉其任意派生类异常。

#p#分页标题#e#

这种派生类与基类(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子句能捕捉任何范例的指针范例异常:

#p#副标题#e#

catch (const void*) … //捕捉任何指针范例异常

通报参数和通报异常间最后一点不同是catch子句匹配顺序老是取决于它们在措施中呈现的顺序。因此一个派生类异常大概被处理惩罚其基类异常的catch子句捕捉,纵然同时存在有能处理惩罚该派生类异常的catch子句,与沟通的try块相对应。譬喻:

try {
 ...
}
catch (logic_error& ex) { // 这个catch块 将捕捉
 ... // 所有的logic_error
} // 异常, 包罗它的派生类
catch (invalid_argument& ex) { // 这个块永远不会被执行
 ... //因为所有的
} // invalid_argument
// 异常 都被上面的
// catch子句捕捉。

与上面这种行为相反,当你挪用一个虚拟函数时,被挪用的函数位于与发出函数挪用的工具的动态范例(dynamic type)最临近的类里。你可以这样说虚拟函数回收最优适正当,而异常处理惩罚回收的是最先适正当。假如一个处理惩罚派生类异常的catch子句位于处理惩罚基类异常的catch子句前面,编译器会发出告诫。(因为这样的代码在C++里凡是是不正当的。)不外你最好做好预先防御:不要把处理惩罚基类异常的catch子句放在处理惩罚派生类异常的catch子句的前面。象上面谁人例子,应该这样去写:

try {
 ...
}
catch (invalid_argument& ex) { // 处理惩罚 invalid_argument
 ... //异常
}
catch (logic_error& ex) { // 处理惩罚所有其它的
 ... // logic_errors异常
}

综上所述,把一个工具通报给函数或一个工具挪用虚拟函数与把一个工具做为异常抛出,这之间有三个主要区别。第一、异常工具在通报时总被举办拷贝;当通过传值方法捕捉时,异常工具被拷贝了两次。工具做为参数通报给函数时不需要被拷贝。第二、工具做为异常被抛出与做为参数通报给函数对比,前者范例转换比后者要少(前者只有两种转换形式)。最后一点,catch子句举办异常范例匹配的顺序是它们在源代码中呈现的顺序,第一个范例匹配乐成的catch将被用来执行。当一个工具挪用一个虚拟函数时,被选择的函数位于与工具范例匹配最佳的类里,纵然该类不是在源代码的最前头。

 

    关键字:

天才代写-代写联系方式