C++ 编程规范 7 异常与错误处理
来源:互联网 发布:python datetime 时区 编辑:程序博客网 时间:2024/06/05 09:19
7 异常与错误处理
7.1 异常
异常是C++语言的一个强大特性,在正确使用之前需要深入了解,以及使用异常代码的上下文。
原则7.1 减少不必要的异常
说明:异常对编码技能要求更高,使用中容易出错,首先从安全性角度考虑,尽量少用或者不用异常。
相比返回错误,异常的优点:
异常可以集中捕捉,错误检测与算法处理相分离,算法逻辑更清晰;而返回错误在每个返回点都
要进行检测与错误处理,代码逻辑分散。
异常的约束更强,用户不能忽略抛出的异常,否则程序默认会被终止,而返回错误则可能被忽略。
异常的缺点也很明显:
必须检查所有调用点是否可能抛出异常,在抛出后必须正确处理状态和资源变量等,否则可能导
致对象状态不正确或者资源泄露等。例如:如果f()依次调用了g()和h(),h抛出被f捕获的异常,
g就要当心了,避免资源泄露。
必须清楚可能抛出的所有异常,并在合适的地方捕捉,如果遗漏通常会导致程序被终止。
使用异常很难评估程序的控制流,代码很难调试。
目标文件变大,编译时间延长,性能下降。
若对异常缺乏充分理解,可能会在不恰当的时候抛出异常, 或在不安全的地方从异常中恢复。
适用异常的几个场景:
出现“不应该出现的”失败,且不能被忽略必须处理,比如分配内存失败。
上层应用决定如何处理在底层嵌套函数中 “不可能出现的”失败。
错误码难以通过函数的返回值或参数返回,比如流。
许多第三方C++库使用异常,必须在系统边界与第三方C++库结合处使用异常便于跟这些库集成。
在测试框架中使用异常很方便。
规则7.1 构造和析构函数不能抛出异常
说明:如果构造和析构函数执行失败则无法安全地撤销和回滚,故这些函数不能向外抛出异常。
为了降低复杂性,建议在这类函数中实现最简单的逻辑。
规则7.2 通过传值的方式抛出,通过引用的方式捕获
说明:抛出异常时,如果抛出指针,谁释放指针就成为问题。捕捉时如果是传值,会存在拷贝,拷贝可
能不成功(比如异常是由于内存耗尽造成的),而且拷贝得不到派生类对象,因为在拷贝时,派生类对象
会被切片成为基类对象。
规则7.3 确保抛出的异常一定能被捕捉到
说明:异常未被捕捉到,系统的默认行为是终止程序运行,所以要确保程序产生的异常都能被捕捉。
规则7.4 确保异常发生后资源不泄漏
说明:异常发生后,当前代码执行序列被打断,需要查看分配的内存、文件和内核句柄等资源是否正确
释放,避免资源泄漏,尤其每个可能的返回点是否正确释放资源。
示例:如下代码存在内存泄漏
int PortalTransformer::transRLS
{
RLS_Service* service = NULL;
NEW( service, RLS_Service );
parser->adoptDocument();//失败时会抛异常
//....
delete service;
service =NULL;
return 0;
}
调用adoptDocument出现的异常没有在函数transRLS里面被捕获,而是在父函数里面捕获了异常的派生
类。如果发生异常,则NEW( service, RLS_Service )分配的内存泄漏。
解决方案:在函数transRLS里面捕获adoptDocument的异常,如果发生异常,则删除指针service 。
规则7.5 独立编译模块或子系统的外部接口禁止抛异常
说明:异常处理没有普遍通用的二进制标准,所以不允许跨模块抛异常。
7.2 错误处理策略
原则7.2 建立合理的错误处理策略
说明:这里所说的错误指运行时错误,并非模块内部的编程和设计错误。模块内部的编程和设计错误
应该通过断言标记。
在设计早期确定错误处理策略,包括:鉴别,严重程度,错误检查,错误处理,错误传递,错误报告
方案。
错误鉴别:对每个实体(函数、类、模块),记录该实体内部和外部的不变式、前置条件、后置条件以
及它支持的错误安全性保证。
错误严重程度:对于每个错误,标明严重级别。
错误检查:对于每个错误,记载哪些代码负责检查它。
错误处理:对于每个错误,标明负责处理它的代码。
错误报告:对于每个错误,标明合适的报告方法。
错误传递:对每个模块,标明使用什么编程机制传递错误,如C++异常、CORBA异常、返回值。
错误处理策略应该只在模块边界改变。如果模块内外所使用的策略不同,则所有模块入口函数都要直
接负责由内到外的策略转换。例如,在一个内部使用C++异常,但提供C语言的API边界的模块中,所有
C语言的API必须用catch(„)捕获所有异常并将其转换为错误代码。
原则7.3 离错误最近的地方处理错误或转换错误
说明:当函数检查到一个自己无法解决的错误,而且会使函数无法继续执行的时候,就应该报告错误。
如果缺乏处理的上下文,应该向上传播错误。
规则7.6 错误发生时,至少确保符合基本保证;对于事务处理,至少符合强保证;对于原子操作,符
合无错误保证
说明:基本保证是指访问对象时的状态都是正确的;强保证是对基本保证的增强,不仅要状态正确,
而且当失败时状态要回滚到操作前的状态,要么成功要么什么都不做;无错误保证是不能出现失败。
编码中严格遵循此原则,会极大提升程序的健壮性。
符合基本保证的代码示例:如下代码是解析输入流到对象,流抛出异常方式呈报错误
void CMessage::Parse(IStream* input)
{
try
{
m_uiMessageLen = input.ReadInteger();//失败会抛出异常
if (0 == m_uiMessageLen || m_uiMessageLen>MAX_MESSAGE_LEN)
{
throw invalid_argument("Invalid message len in CMessage::Parse ");
}
m_pMessage = new char[m_uiMessageLen];//失败抛出异常
input.Read(m_pMessage, m_uiMessageLen);//失败抛出异常
//.....
}
catch (const exception &exp){
ResetContent();//把对象的所有字段都设置为无效
throw exp;
}
return;
}
上例确保对象字段的值要么都有效要么都无效,不会出现部分有效部分无效的情况,否则必须处理异
常被抛出时的对象状态,给调用者带来麻烦,容易遗漏。故接口应该至少符合基本保证。
可以把该函数改造为符合强保证,如下:
void CMessage::Parse(IStream* input)
{
CMessage temp;
try
{
temp .m_uiMessageLen = input.ReadInteger();//失败会抛出异常
if (0 == temp .m_uiMessageLen || temp .m_uiMessageLen>MAX_MESSAGE_LEN)
{
throw invalid_argument("Invalid messageLen in CMessage::Parse ");
}
temp .m_pMessage = new char[temp .m_uiMessageLen];//失败抛出异常
input.Read(temp .m_pMessage, temp .m_uiMessageLen);//失败抛出异常
//.....
}
catch (const exception &exp){
temp .ResetContent();//把对象的所有字段都设置为无效
throw exp;
}
swap(temp);//成功后执行swap
return;
}
失败后,对象的状态不受影响;而成功后,执行的swap必须符合无错误保证,否则此函数是无法支持
强保证的。
- C++编程规范 错误处理与异常
- C++ 编程规范 7 异常与错误处理
- C++编程规范之错误处理与异常(读书笔记)
- 异常与错误处理
- 错误与异常处理
- 异常与错误处理
- 错误与异常处理
- 错误与异常处理
- 错误与异常处理
- 异常处理与错误处理
- 窥探 Swift 编程之错误处理与异常抛出
- 窥探Swift编程之错误处理与异常抛出
- 编程中的错误处理和异常处理
- 错误与异常处理---组件
- WCF异常与错误处理
- php错误与异常处理
- 异常与错误的处理
- 错误与异常处理入门
- C++编程规范 5 作用域、模板和C++其他特性
- ZZ 常用算法经典代码(C++版)
- 使用AES算法对文件进行加密/解密的操作(JAVA)
- C++ 6 资源分配和释放
- 一个是阆苑仙葩,一个是美玉无瑕
- C++ 编程规范 7 异常与错误处理
- C++编程规范 8 标准库
- C++编程规范 9 程序效率
- Linux内核数据包处理流程-数据包接收(2)
- WebApp与Native App有何区别
- android 图片平铺实现
- C++编程规范 10并发
- 鸡蛋饭
- C++编程规范 11风格