赞
踩
使用throw抛出的异常称为异常变量
throw的异常是有类型的,可以是数字、字符串、类对象。
throw的异常是有类型的,catch需严格匹配异常类型。
在C++中执行throw语句时,其操作数的结果作为对象被拷贝构造为一个新的对象,放在内存的特殊位置(既不是堆也不是栈,Windows上是放在“线程信息块TIB”中)。
这个新的对象由本级的try所对应的catch语句逐个做类型匹配;如果匹配不成功,则与本函数的外层catch语句依次做类型匹配;如果在本函数内不能与catch语句匹配成功,则递归回退到调用栈的上一层函数内从函数调用点开始继续与catch语句匹配。
重复这一过程直到与某个catch语句匹配成功或者直到主函数main()都不能处理该异常。
所以,throw语句抛出的异常对象不同于一般的局部对象
一般的局部对象会在其作用域结束时被析构。
而throw语句抛出的异常对象驻留在所有可能被激活的catch语句都能访问到的内存空间中。
throw语句抛出的异常对象在匹配成功的catch语句的结束处被析构
使用值传递的方式来捕获异常时,将异常对象返回,会触发拷贝构造,此时需要系统将异常对象拷贝两次,效率较低
#include <iostream> using namespace std; class MyException { public: MyException(){ cout << "异常变量构造" << endl; }; MyException(const MyException & e) { cout << "拷贝构造" << endl; } ~MyException() { cout << "异常变量析构" << endl; } }; void DoWork() { throw MyExecption(); } void test() { try { DoWork(); } catch (MyException e) { cout << "MyException e捕获 异常" << endl; } } int main() { test(); return 0; }
而且还会产生slicing problem,派生类的异常对象被视为基类异常被捕获,调用的方法将会变为基类方法
当把一个派生类对象赋给一个基类对象时(并不是使用父类指针或引用接收子类对象),会发生对象切割。(另外用基类对象强制转换派生类对象也会)
接收值传递的返回值时也会发生对象切割
指针传递不会进行异常的拷贝,但是异常对象在函数中定义,函数抛出后将会被销毁,当catch捕获到的指针指向的异常早都被释放掉了,此时我们的指针实际是一个“野”指针,没有任何用的(尽管他还能访问数据,但已经变成了”野“指针)。
#include <iostream> using namespace std; class MyException { public: MyException(){ cout << "异常变量构造" << endl; }; MyException(const MyException & e) { cout << "拷贝构造" << endl; } ~MyException() { cout << "异常变量析构" << endl; } }; void DoWork() { MyException m; cout<<"抛出异常前地址为:"<<&m<<endl; throw &m; } void test() { try { DoWork(); } catch (MyException *e) { cout << "MyException *e捕获 异常" << endl; cout << "抛出异常后使用指针接收到的地址e="<<e<<endl; } } int main() { cout << "-----------------使用指针----------------"<<endl; test(); return 0; }
可以使用在堆区创建对象的方法,使用new关键字创建返回的异常对象,这样就不会被系统自动释放,但是需要使用delete手动释放
#include <iostream> using namespace std; class MyException { public: MyException(){ cout << "异常变量构造" << endl; }; MyException(const MyException & e) { cout << "拷贝构造" << endl; } ~MyException() { cout << "异常变量析构" << endl; } }; void DoWork1() { MyException m; cout<<"抛出异常前创建一个对象地址为:"<<&m<<",抛出异常对象时依旧抛出匿名对象"<<endl; throw MyException(); } void DoWork2() { throw MyException(); } void test1() { try { DoWork1(); } catch (MyException &e) { cout << "MyException &e捕获 异常" << endl; cout << "抛出异常后使用指针接收到的地址e="<<&e<<endl; } } void test2() { try { DoWork2(); } catch (MyException &e) { cout << "MyException &e捕获 异常" << endl; cout << "抛出异常后使用指针接收到的地址e="<<&e<<endl; } } int main() { cout << "-----------------使用引用(匿名对象传递)----------------"<<endl; test1(); cout << "-----------------使用引用(匿名对象传递)----------------"<<endl; test2(); return 0; }
使用引用接收异常的匿名对象时,实际返回的并非是函数中的局部对象,而是系统自动拷贝到额外空间的特殊对象,这个对象在抛出时创建在匹配成功的catch执行完后释放
通过引用捕获异常可以避免slicing异常问题;
对象切割问题是由于静态编联导致的,使用引用和指针不会发生
异常变量是在函数中抛出的,属于局部变量,在函数的上层函数中捕获的,中间涉及到局部变量的声明周期
通过上述已经知道使用引用接收异常的匿名对象时,实际返回的并非是函数中的局部对象,而是系统自动拷贝到额外空间的特殊对象,这个对象在抛出时创建在匹配成功的catch执行完后释放了,那为什么拷贝时不调用拷贝构造呢?
#include <iostream> using namespace std; class MyException { public: MyException(){ cout << "异常变量构造" << endl; }; MyException(const MyException & e) { cout << "拷贝构造" << endl; } ~MyException() { cout << "异常变量析构" << endl; } }; int main() { MyException(); //匿名对象调用后不使用,编译器将会调用他的析构函数将其释放 cout<<"-----------------------"<<endl; MyException m = MyException();//按说这里应该会有匿名对象的构造和对匿名对象的拷贝构造,但实际上编译器对其进行了优化 cout<<"-----------------------"<<endl; return 0; }
编译器会尽量少的调用拷贝构造函数,从而不去调用拷贝构造函数而是将匿名对象直接赋值给对象引用变量(没有定义析构函数时可能会有例外),即不在重复的将匿名对象作为构造方法参数调用声明对象的构造方法
所以使用匿名对象返回异常对象时,系统自发的拷贝特殊对象行为将不会再次调用拷贝构造
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。