从语法上看,在函数里声明参数与在catch子句中声明参数是一样的,catch里的参数可以是值类型,引用类型,指针类型。例如: try { ..... } catch(A a) { } catch(B& b) { } catch(C* c) { } 尽管表面是它们是一样的,但是编译器对二者的处理却又很大的不同。调用函数时,程序的控制权最终还会返回到函数的调用处,但是抛出一个异常时,控制权永远不会回到抛出异常的地方。 class A; void func_throw() { A a; throw a; //抛出的是a的拷贝,拷贝到一个临时对象里 } try { func_throw(); } catch(A a) //临时对象的拷贝 { } 当我们抛出一个异常对象时,抛出的是这个异常对象的拷贝。当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。此时对象会丢失RTTI信息。 异常是其它对象的拷贝,这个事实影响到你如何在catch块中再抛出一个异常。比如下面这两个catch块,乍一看好像一样: catch (A& w) // 捕获异常 { // 处理异常 throw; // 重新抛出异常,让它继续传递 } catch (A& w) // 捕获Widget异常 { // 处理异常 throw w; // 传递被捕获异常的拷贝 } 第一个块中重新抛出的是当前异常(current exception),无论它是什么类型。(有可能是A的派生类) 第二个catch块重新抛出的是新异常,失去了原来的类型信息。 一般来说,你应该用throw来重新抛出当前的异常,因为这样不会改变被传递出去的异常类型,而且更有效率,因为不用生成一个新拷贝。 看看以下这三种声明: catch (A w) ... // 通过传值 catch (A& w) ... // 通过传递引用 catch (const A& w) ... //const引用 一个被异常抛出的对象(总是一个临时对象)可以通过普通的引用捕获;它不需要通过指向const对象的引用(reference-to-const)捕获。在函数调用中不允许转递一个临时对象到一个非const引用类型的参数里,但是在异常中却被允许。 // 因为临时对象会过一会儿会释放了,那么函数中该非const引用就引用一个不存在的对象了。 回到异常对象拷贝上来。我们知道,当用传值的方式传递函数的参数,我们制造了被传递对象的一个拷贝,并把这个拷贝存储到函数的参数里。同样我们通过传值的方式传递一个异常时,也是这么做的当我们这样声明一个catch子句时: catch (A w) ... // 通过传值捕获 会建立两个被抛出对象的拷贝,一个是所有异常都必须建立的临时对象,第二个是把临时对象拷贝进w中。实际上,编译器会优化掉一个拷贝。同样,当我们通过引用捕获异常时, catch (A& w) ... // 通过引用捕获 catch (const A& w) ... //const引用捕获 这仍旧会建立一个被抛出对象的拷贝:拷贝是一个临时对象。相反当我们通过引用传递函数参数时,没有进行对象拷贝。话虽如此,但是不是所有编译器都如此。VS200就表现很诡异。
通过指针抛出异常与通过指针传递参数是相同的。不论哪种方法都是一个指针的拷贝被传递。你不能认为抛出的指针是一个指向局部对象的指针,因为当异常离开局部变量的生存空间时,该局部变量已经被释放。Catch子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免。
另外一个重要的差异是在函数调用者或抛出异常者与被调用者或异常捕获者之间的类型匹配的过程不同。在函数传递参数时,如果参数不匹配,那么编译器会尝试一个类型转换,如果存在的话。而对于异常处理的话,则完全不是这样。见一下的例子:
void func_throw() { CString a; throw a; //抛出的是a的拷贝,拷贝到一个临时对象里 }
try { func_throw(); } catch(const char* s) { } 抛出的是CString,如果用const char*来捕获的话,是捕获不到这个异常的。 尽管如此,在catch子句中进行异常匹配时可以进行两种类型转换。第一种是基类与派生类的转换,一个用来捕获基类的catch子句也 可以处理派生类类型的异常。反过来,用来捕获派生类的无法捕获基类的异常。 第二种是允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的catch子句能捕获任何类型的指针类型异常: catch (const void*) ... //可以捕获所有指针异常 另外,你还可以用catch(...)来捕获所有异常,注意是三个点。
传递参数和传递异常间最后一点差别是catch子句匹配顺序总是取决于它们在程序中出现的顺序。因此一个派生类异常可能被处 理其基类异常的catch子句捕获,这叫异常截获,一般的编译器会有警告。
class A { public: A() { cout << "class A creates" << endl; } void print() { cout << "A" << endl; } ~A() { cout << "class A destruct" << endl; } }; class B: public A { public: B() { cout << "class B create" << endl; } void print() { cout << "B" << endl; } ~B() { cout << "class B destruct" << endl; }
}; void func() { B b; throw b; } try { func(); } catch( B& b) //必须将B放前面,如果把A放前面,B放后面,那么B类型的异常会先被截获。 { b.print(); } catch (A& a) { a.print() ; }
相反的是,当你调用一个虚拟函数时,被调用的函数位于与发出函数调用的对象的动态类型(dynamic type)最相近的 类里。你可以这样说虚拟函数匹配采用最优匹配法,而异常处理匹配采用的是最先匹配法。
在函数调用中不允许转递一个临时对象到一个非const引用类型的参数里,但是在异常中却被允许。 // 因为临时对象会过一会儿会释放了,那么函数中该非const引用就引用一个不存在的对象了。而当为const引用类型时,该临时对象并不会过一会儿释放,而是与函数中该参数生命周期一样,这样就可以引用该临时对象了。
class A { public: A() { m_Int = 10; }
public: int m_Int; };
A GetInt() { A a; return a; }
void Test( A& pA ) { cout << pA.m_Int << endl; // 还会输出10 return; }
int main() { A& pA = GetInt(); cout << pA.m_Int << endl; // 可以输出10,是因为pA为引用,GetInt返回的临时变量不会马上释放,生命周期与pA一致
Test( GetInt() ); // 这里不是传递一个临时变量到非const引用的参数,怎么也行呢?
system("pause"); return 1; } (责任编辑:好模板) |