[c++] Exceptions

异常处理

一、相关资料

【C++】异常简述(一):C语言中的异常处理机制

【C++】异常简述(二):C++的异常处理机制 

【C++】异常简述(三):补充之如何看待C++异常

[ 之后,针对本章节、根据链接再进行一次系统的学习 ]

 

基本结构

try {
    throw logic_error{"blah"};
}
catch (exception) { // caught here!
} catch (logic_error) { // not here! }

 

百家讲坛 - "必要性"

[1]

为什么需要异常机制:https://blog.csdn.net/K346K346/java/article/details/50087193

C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:

(a) 一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);

(b) 另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以在用户的代码里处理了,不用留给库去发现)。

Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。

[2]

C++ 引入异常的原因之一是:为了能让构造函数报错(析构函数不能抛异常这是大家都知道的常识),毕竟构造函数没有返回值,没有异常的话调用方如何得知对象构造是否成功呢?

[3]

对使用 C++ 异常处理应具有怎样的态度?

到了异常,一般就直接让本次操作失效,保存状态好了.

 

 

定义异常

一、内置 Error类型

 

下表是对上面层次结构中出现的每个异常的说明:

异常描述
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
- std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
- std::invalid_argument 当使用了无效的参数时,会抛出该异常。
- std::length_error 当创建了太长的 std::string 时,会抛出该异常。
- std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
- std::overflow_error 当发生数学上溢时,会抛出该异常。
- std::range_error 当尝试存储超出范围的值时,会抛出该异常。
- std::underflow_error 当发生数学下溢时,会抛出该异常。

 

二、自定义异常类型

通过继承和重载 exception 类来定义新的异常。

#include <iostream>
#include <exception>
using namespace std;
// 竟然是个struct
struct MyException : public exception { const char* what () const throw
{ return "C++ Exception"; } };

// -------------------------------------------------
int main() { try { throw MyException(); } catch(MyException& e) { std::cout << "MyException caught" << std::endl; std::cout << e.what() << std::endl; } catch(std::exception& e) { // 其他的错误 } }

类可以继承,结构体也可以继承.

#include <iostream>  
using namespace std;
// class MyException : public exception { public: const char* what() const throw ()   // <---- { std::cout << "my exception" << std::endl; return NULL; } }; void f1(bool flag = true) { if (flag) throw MyException(); } void f2(bool flag = true) throw () { if (flag) throw MyException(); }
-----------------------------------------------------
int main(void) { try { f1(); }
catch (...) {
; }
std::cout
<< "f1()异常将被捕获,不会 abort,将继续执行" << std::endl; try { f2(); }
catch (...) {
; }
std::cout
<< "f2()异常不会被捕获,程序将会 abort,将不会执行该条语句" << std::endl; return 0; }

 

 

使用异常

Catch-by-reference is BETTER

Problems with Catch-by-value:

    • inefficient due to object copying
    • causes the slicing problem, and
    • cannot exploit polymorphism and dynamic binding

Catch-by-reference avoids all these problems.

catch的参数是引用,自然就不需要拷贝了。

 

  • Catch-by-Value

#include <iostream>

using namespace std;

class X {
public: X() { std::cout << "X constructed" << std::endl; } X(const X &x) { std::cout << "X copy-constructed" << std::endl; } ~X() { std::cout << "X destructed" << std::endl; } };
void g() { throw X{}; } void f() { try { g(); } catch (X x) {  // 改为catch(X &x), 其他同理 std::cout << "caught in f; rethrow" << std::endl; throw; } }
int main() { cout << "Hello World!" << endl; try { f(); } catch (X x) {  // 这里又是一次 浪费时间的 copy std::cout << "caught in main" << std::endl; } return 0; }

Result:

 

  • Catch-by-Reference

改为reference后,concise了不少!

  

  • Stack Unwinding

关于new object这件事上,Obversely, this avoids memory leaks but is messy.

便有了Smart Pointer? 这里是初始化列表的用法。

 

Stack Exception Safety

如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行,这比基于异常机制的throw()在效率上会高一些。

这是因为异常机制会带来一些额外开销,比如函数抛出异常,会导致函数栈被依次地展开(unwind,并依帧调用在本帧中已构造的自动变量的析构函数等。

Common Levels of Exception Safety

 

End.

posted @ 2016-12-15 18:05  郝壹贰叁  阅读(765)  评论(0编辑  收藏  举报