【M12】了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

时间:2023-11-22 09:54:44

1、方法参数的声明语法和catch语句的语法是一样的,你可能会认为主调方法调用一个方法,并向其传递参数,与抛出一个异常传递到catch语句是一样的,是的,有相同之处,但也有更大的不同。

2、主调方法调用一个方法,控制权转移,被调方法执行完,控制权最终还会返回到主调方法。但是,抛出异常到达catch语句,控制权不会再回到抛出端。

3、那么问题来了,抛出异常到达catch语句,控制权不再会到抛出端,这意味着,抛出的异常离开了作用域,自动销毁,那么catch语句还怎么捕获异常对象呢?

  解决办法是:建立一个临时对象,对抛出的异常进行copy构造。特别注意的是,不管什么情况,这个临时对象是必不可少的。根本原因就是,抛出端的异常对象销毁了,必须建立一个临时对象,才能传递到catch语句。而对于调用方法,如果是传递引用,不需要复制对象,因为主调方法中的局部对象并没有销毁。

4、即使抛出的异常没有销毁,抛出的异常对象到catch语句,中间也有一个临时对象。

5、这意味着,catch语句没有办法修改抛出端的异常对象,修改的只是副本,也就是临时对象。

6、从抛出异常到catch语句,之间建立一个临时对象,这个临时对象的类型,是抛出异常对象的表面类型。这就意味着,如果抛出的异常对象,表面类型与真实类型不一致,会造成对象切割。

7、这就导致下面的问题:抛出一个子类,在catch(Base& b)的语句中匹配成功,此时,b的表面类型和真实类型不一致,如果从新抛出b,建立的临时对象就是父类对象,也就是对象切割。也就是说,重新抛出的异常更加抽象,这显然不合理,那怎么办呢?直接使用throw,重新抛出当前的异常对象。并且不再产生一个新的临时对象。

8、catch语句中可以是传值,传引用,传const引用。对于传值,从临时对象到catch语句中形参,还要进行一次copy构造。因此,应该使用传引用,避免这一次的copy构造。那么,对于传const引用呢?

9、我们知道对于内置类型的临时对象,是不能修改的,但是,对于exception是个例外。思考一下,为什么对于内置类型的临时对象不能修改?

  临时对象的来源:隐式类型转化,方法返回值,抛出异常。

  对于隐式类型转化,临时对象不可修改。如果可修改,赋值给non-const引用,程序员修改这个引用,企图修改原数据,但实际上,修改的是临时对象,这与程序员的期望不符合,造成错觉。因此,禁止这种行为。

  对于方法返回值,产生的临时对象不可修改。这是为了,禁止客户对方法返回值进行赋值。

  但是,对于抛出异常产生的临时对象,可以修改。为什么?因为有这种需求:对抛出的异常追加一些额外信息。

10、现在考虑指针,抛出一个异常对象,catch语句的参数是指针,不能匹配成功,因为类型不吻合。不能把一个对象赋值给指针,必须取地址,赋值给指针。

11、继续思考,抛出一个指针,catch指针,相当于传递指针。这种情况,要保证指针指向的对象没有销毁。

12、调用方法,参数可以进行隐式类型转换。比如int转化为double。但是,对于抛出异常,不能进行这类的隐式转换。抛出异常可以进行的转换只有两种:子类转化为父类;有型指针转换为无型指针。

13、调用方法是最优匹配,而捕获异常是最先匹配。这就意味着,catch语句捕获的异常应该是,由具体到抽象,否则造成死代码。