尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++(17)——异常

C++(17)——异常 异常1. C语言传统的处理错误的方式1. 终止程序如 assert2. 返回错误码2. C异常概念异常是一种处理错误的方式当一个函数发现自己无法处理的错误时就可以抛出异常让函数的直接或间接的调用者处理这个错误throw当问题出现时通过throw关键字抛出异常catch用于捕获异常可以有多个catch进行捕获trytry块中代码表示将被激活的特定异常后面通常跟着一个或多个catch块try { // 保护的标识代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块 }catch( ExceptionName eN ) { // catch 块 }3. 异常的使用3.1 异常的抛出和捕获3.1.1 异常的抛出和匹配原则1. 异常是通过抛出对象而引发的该对象的类型决定了应该激活哪个catch的处理代码2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个3. 抛出异常对象后会生成一个异常对象的拷贝因为抛出的异常对象可能是一个临时对象这个拷贝的临时对象会在被catch以后摧毁类似于函数传值返回的处理4. catch(...)可以捕获任意类型的异常但是不知道异常错误是什么5. 抛出的派生类对象可以使用基类捕获3.1.2 在函数调用链中异常1. 先检查throw本身是否在try块内部如果是再查找匹配的catch语句如果有匹配的则调到catch的地方进行处理2. 没有匹配的catch则退出当前函数栈继续在调用函数的栈中进行查找匹配的catch3. 如果到main函数的栈依旧没有匹配则终止程序。上述过程称为栈展开。实际中在最后都要加上一个catch(...)捕获任意类型的异常以防止程序被终止4. 找到匹配的catch子句并处理后会继续沿着catch子句后面继续执行3.2 异常的重新抛出有可能单个的catch不能完全处理一个异常可以通过重新抛出将异常传递给更上层的函数进行处理double Division(int a, int b) { // 当b 0时抛出异常 if (b 0) { throw Division by zero condition!; } return (double)a / (double)b; } void Func() { // 这里可以看到如果发生除0错误抛出异常另外下面的array没有得到释放。 // 所以这里捕获异常后并不处理异常异常还是交给外面处理这里捕获了再 // 重新抛出去。 int* array new int[10]; try { int len, time; cin len time; cout Division(len, time) endl; } catch (...) { cout delete [] array endl; delete[] array; throw; } // ... cout delete [] array endl; delete[] array; } int main() { try { Func(); } catch (const char* errmsg) { cout errmsg endl; } return 0; }3.3 异常安全1. 构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常否则可能导致对象不完整或没有完全初始化2. 析构函数主要完成资源的清理最好不要在析构函数中抛出异常否则可能导致资源泄漏3.4 异常规范// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常 void fun() throw(ABCD); // 这里表示这个函数只会抛出bad_alloc的异常 void* operator new (std::size_t size) throw (std::bad_alloc); // 这里表示这个函数不会抛出异常 void* operator delete (std::size_t size, void* ptr) throw(); // C11 中新增的noexcept表示不会抛异常 thread() noexcept; thread (thread x) noexcept;若无异常接口声明则此函数可以抛出任何类型的异常4. 自定义异常体系实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理定义一套继承的规范体系以防随意抛异常。例如// 服务器开发中通常使用的异常继承体系 class Exception { public: Exception(const string errmsg, int id) :_errmsg(errmsg) ,_id(id) {} virtual string what() const { return _errmsg; } protected: string _errmsg; int _id; }; class HttpServerException : public Exception { public: HttpServerException(const string errmsg, int id, const string type) :Exception(errmsg, id) , _type(type) {} virtual string what() const { string str HttpServerException:; str _type; str :; str _errmsg; return str; } private: const string _type; }; void HttpServer() { // ... srand(time(0)); if (rand() % 3 0) { throw HttpServerException(请求资源不存在, 100, get); } else if (rand() % 4 0) { throw HttpServerException(权限不足, 101, post); } } int main() { while (1) { this_thread::sleep_for(chrono::seconds(1)); try{ HttpServer(); } catch (const Exception e) // 这里捕获父类对象就可以 { // 多态 cout e.what() endl; } catch (...) { cout Unkown Exception endl; } } return 0; }5. C标准库的异常体系C提供了一系列标准的异常它们以父子类层次结构组织起来的6. 异常的优缺点优点1. 可以更清晰准确的展示出错误的各种信息甚至包含堆栈调用的信息可以帮助更好的定位程序的bug2. 异常体系中在函数调用链中深层的函数返回了错误不需要层层返回直接可以拿到错误3. 部分函数使用异常更好处理比如T operator[]这样的函数如果pos越界了只能使用异常或者直接终止程序处理无法通过返回值表示错误缺点1. 异常会导致程序的执行流乱跳非常混乱导致跟踪调试分析程序比较困难主要缺点2. 异常会有一些性能开销。在现代硬件速度很快的情况下可以基本忽略不计3. C没有垃圾回收机制资源需要自己管理。异常非常容易导致内存泄漏、死锁等异常安全问题需要RAII来处理资源的管理问题学习成本较高4. C标准库的异常体系定义得不好导致大家自定义各自的异常体系比较混乱5. 异常需要规范使用否则会很麻烦。两点规范1. 抛出异常类型都继承自一个基类 2. 使用func() throw();的方式规范化总结异常利大于弊鼓励使用异常并且OO的语言都是用异常处理错误的
返回列表