1️⃣ 函数参数和catch语句的声明方法

1
class Widget {...};					 //某个class,细节不重要。
1
2
3
4
5
6
7
8
9
10
11
12
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* 的 exceptions。
catch (const Widget *pw)...

2️⃣ 相同点

  • 函数参数和exceptions的传递方式都有三种:by valueby referenceby pointer

3️⃣ 不同点

  1. 对程序的控制权

    • 当调用函数时,控制权最终会回到调用端
    • 当抛出一个exception,控制权不会回到调用端
  2. 不论被捕捉的exception是以by value,还是by reference 方式传递,都会发生copy行为。

    1
    2
    3
    4
    5
    6
    7
    8
    //此函数从一个stream中读取一个widget。
    istream operator>>(istream& s, Widget& w);
    void passAndThrowWidget ()
    {
    Widget localwidget;
    cin >> localWidget; //将localwidget 传给operator>>。
    throw localWidget; //将localwidget抛出成为一个exception。
    }
    • localWidget被交到operator>>函数手中,并没有发生copy行为,而是operator>>内的 reference w被绑定于localWidget身上。此时,对w做的事情,其实是施加于localWidget身上的。
    • 不论被捕捉的exception是以by valueby reference方式传递,都会发生localWidget的复制行为,而交到<font style="background-color:#FCE75A;">catch</font>子句手上的正是那个副本。一旦控制权离开passAndThrowWidgetlocalWidget便离开了作用域,于是localWidget destructor会被调用。
  3. 可以将一个临时对象传递给exception

  4. catch 语句总是按照出现顺序做匹配尝试

    • 缺点

      • try语句块中分别有针对base class而设计和针对derived class而设计的catch子句,一个derived class exception仍有可能被“针对base class而设计的catch子句”处理掉。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        try {
        ...
        }

        catch (logic_error& ex) { //此语句块将捕捉所有的
        ... // logic_error exceptions,
        } //甚至包括其 derivedtypes。

        catch (invalid_argument& ex) { //此语句块绝不会执行起来,
        ... // 因为所有的invalid_argument
        } //exceptions都会被上述子句捕捉。
    • 与虚函数的差异

      • 虚函数执行的是“best fit”(最佳吻合)策略
      • exception执行的是”first fit“(最先吻合)策略

4️⃣ 进一步理解

  • exception objects必定会造成复制行为,这导致其效率不高。

  • 当对象被复制当做一个exception,复制行为是由对象的copy constructor执行的。这个 copy constructor相应于该对象的“静态类型”而非“动态类型”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Widget { ... };
    class Specialwidget : public Widget ( ... };

    void passAndThrowWidget ()
    {
    Specialwidget localSpecialwidget;
    ...
    Widget& rw = localSpecialwidget; // rw 代表一个 Specialwidget。
    throw rw; //抛出一个类型为 Widget 的 exception。
    }
    • 这里抛出的是一个widget exception——虽然rw实际代表的是一个Special widget
    • 这是因为rw的静态类型是widget而非Specialwidget
    • rw虽然代表一个SpecialWidget,编译器却不关心这个事实,它们关心的是rw的静态类型。

5️⃣ 如何在 catch 语句块内再次传播 exception

1
2
3
4
5
6
7
8
9
10
11
12
catch (Widget& w)				// 捕捉 widget exceptions。
{
... // 处理 exception。
throw; //重新抛出此exception,
} //使它能继续传播。


catch (Widget& w) // 捕捉 widget exceptions。
{
... // 处理exception。
throw w; // 传播被捕捉的exception
} // 的一个副本。
  • 这两个catch语句块之间唯一的差异就是,前者重新抛出当前的exception,后者抛出的是当前 exception的副本。这两种做法的区别是什么?
    • 第一语句块,重新抛出当前的exception,不会根据其类型之前的类型是什么。
    • 第二语句块,抛出一个新的exception,其类型总是widget,因为那是w的静态类型。
  • 那种做法更好?
    • 一般而言,你必须使用以下语句:throw,才能重新抛出当前的exception。
    • 此外,它也比较有效率,因为不需要产生新的exception object

6️⃣ 三种 catch 子句

1
2
3
catch (Widget w) ...				// 以 by value 的方式捕捉。
catch (Widget& w) ... // 以 by reference 的方式捕捉。
catch (const Widget& w) ... // 以 by reference-to-const 的方式捕捉。
  • “参数传递”和“exception 传播”的另一个区别
    • 一个被抛出的对象,一定是个临时对象。
    • 在函数调用中,将一个临时对象传递给一个non-const reference参数是不允许的;但是对 exception是合法的。

7️⃣ 回到“复制 exception objects”主题

  • <font style="background-color:#FBDE28;">by value</font>方式捕捉<font style="background-color:#FBDE28;">exception</font>,便是对被传递的对象做一个副本。
1
catch (Widget w) ...			// 以 by value 方式捕捉
- 预期付出“被抛出物”的“两个副本”的构造代价。
    * 其中一个构造动作用于“任何`exceptions`都会产生的临时对象”身上。
    * 另一个构造动作用于“将临时对象复制到`catch`语句中的参数`w`”。
  • by reference方式捕捉exception,便是对被传递的对象做一个引用。

    1
    catch (Widget& w) ...			// 以 by reference 方式捕捉
    • 预期付出“被抛出物”的“一个副本”的构造代价。
      • 这里的副本便是指临时对象。由于以by reference方式传递函数参数时并不会发生复制行为,所以“抛出exception”和“传递函数参数”相比,前者会多构造一个“被抛出物”的副本(并于稍后析构)。

8️⃣ exception 与 catch 语句的类型吻合

  • 一般不会发生类型转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void f(int value)
    {
    try {
    if (someFunction()) { // 如果 someFunction()返回true,
    throw value; //就抛出一个int。
    }
    }
    catch (double d) { // 在这里处理类型为double的exceptions。
    ...
    }
    ...
    }
    • try 语句块抛出的int exception绝不会被“用来捕捉double exception”的catch子句捕捉。
    • 后者只能捕捉类型确确实实为doubleexceptions,其间不会有类型转换的行为发生。
    • 所以,如果int exception被捕捉,它一定是被某些其他(也许是外围的)catch子句捕捉的(它们的捕捉类型一定是int或int&,或许再加上const或volatile之类的限定词)。
  • 仅有两种转换可以发生

    • 继承结构中的类转换(inheritance-based conversions)
      • 一个针对base class exceptions编写的catch子句,可以处理类型为derived classexceptions
      • 第二个允许发生的转换是从一个“有型指针”转为“无型指针”,所以一个针对const void*指针而设计的catch子句,可捕捉任何指针类型的exceptioncatch(const void*)