1. 异常Exception
抛出异常:
throw some_exception();
捕获异常:
try {
f();
// ...
} catch (std::exception& e) {
// ...code that handles the error...
}
2. 什么时候使用异常
异常通常发生于运行期间,如申请内存失败,类型转换错误, 文件读写失败,或者主动抛出的异常。 一般适用于逻辑不能再执行下去的情况。
C++中异常还是很有用的, 有的时候没有异常很难办。如构造函数中发生异常, 构造函数没办法返回值, 只能抛异常。构造函数抛异常要十分小心, 后文再分析。
使用异常处理能够代替返回错误码的形式,简化代码。
但是, 异常处理也是有代价的,比起没有异常处理的代码, 据估计,有异常处理的代码“大约膨胀5%~10%,
执行速度亦大约下降这个数”, 如果没有异常发生,这个数字大约是3%。
要不要使用异常,取决于你的程序对执行速度和错误响应速度的要求。
- 如果要求很严格,比如航空领域的飞控系统(人命关天),整个工程中杜绝使用异常处理。
- 一般来说,非必要不使用异常。
- 如果你的程序对以上两点要求不是很严格, 可以适当放宽异常使用的限制,毕竟有的时候异常处理能使代码更加简洁。
3. 以引用方式捕获异常
捕获异常的方式和传递函数参数有些类似, 但也有很大不同。 一般有三种方式:值、引用、指针
try {
cout << "begin" << endl;
static MyException me("test");
throw me;
}
catch( MyException e ) {
cout << "catched exception by value: " << e.what() << endl;
}
catch( MyException* e ) {
cout << "catched exception by pointer: " << e->what() << endl;
}
catch( MyException& e ){
cout << "catched exception by reference: " << e.what() << endl;
}
以上代码为例, 传值的方式抛异常, 异常对象会被复制两次, 一次发生在抛出时,一次发生在捕获时;
传指针和传引用差不多, 有人说传指针的话要用确保离开try block时对象还存在, 比如用static。但我试了一下,不用似乎也可以, 可能编译做了一些隐式的拷贝工作。。maybe。。 但在catch block 中要不要delete这个指针是个问题。
一般最好的方式还是传引用, 引用前不必加const, 不必担心临时变量传递给非const引用, 这点和函数传参不同。
4. 构造函数和析构函数
构造函数中使用异常要十分小心, 因为一个构造函数跑出异常, 这个对象没有被完整的构造出来, 则不会调用析构函数对其析构, 这可能造成动态申请和资源不能被释放。
C++只会析构已构造完成的对象
如以下代码就可能造成内存泄露:
struct B{
B(){
Foo* pF = new Foo; // 1
std::cout << "B()" << std::endl;
}
~B(){
delete pF;
std::cout << "~B()" << std::endl;
}
};
struct A{
B b;
A(){
Foo *pF = new Foo; // 2
Bar *pB = new Bar; // 3
std::cout << "A()" << std::endl;
throw std::exception(); // 4
}
~A(){
delete pF;
delete pB;
std::cout << "~A()" << std::endl;
}
};
A和B的析构函数都不会被调用, 但这本身没有问题。 但如果A或B中有分配在堆上的对象,构造函数跑出异常后,则其可能不会被释放。 即使注释掉//4句, 仍可能有问题, 因为//3句本身就可能抛出异常(std::bad_alloc).
析构函数中永远不要抛异常
[Ref]
https://isocpp.org/wiki/faq/exceptions#why-exceptions
“More Effective C++"
https://stackoverflow.com/questions/32323406/what-happens-if-a-constructor-throws-an-exception/32323458