时间:2022-08-23 10:22:57 | 栏目:C代码 | 点击:次
异常: 异常是面向对象与法处理错误的一种方式
//第2点 T& operator[](int index) { //问题: 当访问的下标index超出容器的范围,此时该返回什么值? //解决方案: 可以再传一个参数,用于获取真正的结果(当输出型参数), 返回值就用于判断是否正确的返回了. //实际上还是很麻烦 }
try { //保护代码(可能会抛出异常的代码) }catch(ExceptionType e1) { //处理异常 }catch(ExceptionType e2) { //处理异常 }catch(ExceptionType eN) { //处理异常 }
· 原则2:函数调用链中的异常 - 栈展开的匹配原则
栈展开:上述的这个沿着调用链去逐个栈中查找匹配的catch语句的过程就是栈展开。
//代码演示:f3()中抛出异常,该异常会被f1()捕捉,而在main函数中的catch是无法捕捉到的 void f3() { throw 123; } void f2() { f3(); } void f1() { try { f2(); cout << "f1() not catched Exception!" << endl; }catch(int& err) { cout << "f1()-err: " << err << endl; } } int main() { try { f1(); cout << "main not catched Exception!" << endl; }catch(int& err) { cout << "main-err: " << err << endl; } return 0; }
注意:
抛出异常对象后,会生成一个异常对象的拷贝(可能是一个临时对象),这个拷贝的临时对象在被catch后会被销毁。异常的执行流执行顺序:从throw抛出异常处跳转到调用链中能够匹配的catch语句中;然后执行catch块中的代码;catch执行完毕后在当前函数栈中顺序执行。(整个调用链中没有匹配的就终止程序)
?可能会存在单个catch不能完全处理一个异常的情况,在经过一些校正处理后,我们希望将该异常交给外层调用链中的函数来处理,此时我们可以通过在catch中重新抛出异常的方式把异常传递给调用链的上层函数处理。
//在SecondThrowException()函数中我们要delete[]动态开辟(new出来)的空间, //如果不使用异常的重新抛出的话,就会造成内存泄漏问题 (也可以使用RAII) void FirstThrowException() { throw "First throw a exception!"; } void SecondThrowException() { int* arr = new int[10]; try { FirstThrowException(); }catch(...) { cout << "Delete[] arr Success!" << endl; delete[] arr; throw; } } void SoluteException() { try { SecondThrowException(); }catch(const char* err) { cout << err << endl; } } int main() { SoluteException(); return 0; }
自定义异常体系实际上就是自己定义的一套异常管理体系,很多公司当中都会自定义自己的异常体系以便于规范的进行异常管理。它主要用到了我们在上面所说的一条规则: 我们只需要抛出派生类对象,然后捕获基类对象就可以了。这样的抛出与捕获方式非常便于异常的处理。
class MyException { public: MyException(string errmsg, int id) :_errmsg(errmsg), _id(id) {} virtual string what() const = 0; //必须放到public下才能让类外定义的成员访问到 protected: int _id; //错误码 string _errmsg; //存放错误信息 //list<StackInfo> _traceStack; //存放调用链的信息 //... }; class CacheException : public MyException { public: CacheException(string errmsg, int id) :MyException(errmsg, id) {} virtual string what() const { return "CacheException!: " + _errmsg; } }; class NetworkException : public MyException { public: NetworkException(string errmsg, int id) :MyException(errmsg, id) {} virtual string what() const { return "NetworkException!: " + _errmsg; } }; class SqlException : public MyException { public: SqlException(string errmsg, int id) :MyException(errmsg, id) {} virtual string what() const { return "SqlException!: " + _errmsg; } }; int main() { try { //抛出任意的派生类对象 throw SqlException("sql open failed", 10); } catch (const MyException& e) //只需要捕获基类对象 { cout << e.what() << endl; //这里实际上完成了一个多态 } catch (...) //走到这里说明出现未知异常 { cout << "Unknown Exception!" << endl; } return 0; }
异常规范的指定是为了让使用者知道函数可能抛出哪些异常,用法:
void func1() throw(); //表示该函数不会抛异常 void func2() noexcept; //等价于throw() 表示该函数不会抛异常 void func3() throw(std::bad_alloc); //表示该函数只会抛出bad_alloc的异常 void func4() throw(int, double, string); //表示该函数会抛出int/double/string类型中的某种异常
C++提供了一系列标准的异常,定义在中,下面是这些异常的组织形式。
异常 | 描述 |
---|---|
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 | 当发生数学下溢时,会抛出该异常。 |
int main() { try { vector<int> v(5); v.at(5) = 0; //v.at(下标) = v[下标] + 抛异常 } catch(const exception& e) { cout << e.what() << endl; } catch(...) { cout << "Unknown Exception!" << endl; } }