使用异常和返回值
1.使用返回值处理错误需要程序员严格的编程作风,程序员不论是否有这种习惯,这都是非常不希望的。
2.正确的异常处理是C++中的一个常识。异常通过发出错误信号,可以让程序代码和错误处理代码分开,而且不会让程序忽略错误。
3.必须了解使用了哪种错误处理方法,返回值还是抛出异常。如果不知道,那么你的程序肯定有问题。
4.异常是基于每个线程而提出并处理的;异常不能被线程忽略,必须被处理;未处理的异常会使进程结束,而不仅仅是线程结束;异常处理在释放栈时会释放所有的栈对象,因此避免了资源的漏洞;异常处理需要大量的额外操作,使得它并不适于经常运行的代码。详细的说,catch块有一些开销(overhead),但是try块有很少的开销;因此只有在抛出异常的时候才会有很多的异常操作开销;你可以抛出任何类型的异常对象,但不包括整数。
5.返回值可以指示正常和不正常的函数运行,但不能阻止线程的继续运行;返回值很容易被忽略;返回值在典型的情况下是一个整数,通常映射符合于一个预定义的值;返回值能高效的传递和接收;
6.所有的非错误的状态信息都应该使用返回值
7.返回值用于大多数情况下可以随意忽略而不会出现问题的错误
8.在循环中的错误处理必须快速,因为异常的额外开销,所以为了得到更好的性能,使用返回值是一个更好的选择。在这种情况下,如果你真要使用异常,可以创建一个函数来将返回值转化未异常。
9.使用于中间语言模块中的错误。
10.使用Windows API的错误处理机制。用SetLastError设置错误代码,通过GetLastError检测这个错误代码。
11.从C++异常处理的观点看,故障应该被认为是错误。
12.你必须使用/Eha调试器选项来扑获使用C++异常操作机制的操作系统异常。
14.Windows结构异常处理:使用_try,_except,_finally,_leave关键字和RaiseException API函数;由Windows支持,不适于其他操作系统;不处理C++对象的解析;作为硬件异常或操作系统异常的结果抛出,也可作为RaiseException函数结果被抛出。
15.C++异常处理:使用try,throw和catch等关键字;仅被C++语言支持;处理C++对象的解析;可以抛出任何类型的C++对象。异常对象可以从标准的异常基本类派生,也可以从任何类派生,或者它们也可以是内置的类型;作为throw语句的结果被抛出。
16.Visual C++ 使用结构异常处理机制实现C++异常。
17.结构异常处理不能处理对象的解析,因此你应该在C++程序中一直使用C++异常。然而,因为C++异常不能处理硬件和操作系统异常,你的程序需要将结构异常转化为C++异常。
18.为了正确处理硬件和操作系统异常,你可以创建自己的异常类并使用_set_se_translator函数安装一个结构异常向C++异常的转化器。
19.不要扑获那些不能恢复所产生问题的转化后的结构异常。
20.在很少抛出异常的情况下使用异常的代价并不是很大,而且这样做确实可以提高性能。
21.异常策略中最重要的一部分实际上就是有一个策略。不要在事后弥补。
22.异常扑获规则:扑获处理器按顺序提供;如果扑获处理器扑获了同一类型或指向同一类型抛出对象的指针,则应扑获异常。如果扑获处理器扑获了一个公共基类或者指向一个公共基类抛出对象的指针,则应扑获异常;一个省略扑获处理器扑获任何类型的异常,因此它总是放在最后。
23.定义一个异常基类来处理程序代码抛出的异常。
class CProgramException : public exception {
public:
CProgramException (const _exString &_what_arg) :
exception(_what_arg) {}
};
使用CProgramException类使得异常处理更加简单,因为可以通过处理这种基类扑获所有的程序中的异常。如果需要的话,也可以使用额外的成员数据全面描述特定的问题。
24.使用auto_ptr或者一个类似的指针类通过限制局部变量的动态分配来自动释放资源:
void LeakFreeFunction (int arg) {
auto_ptr<CMyObject> pObject(new CMyObject(arg));
... // do something that throws an exception
// can still call member functions as normal
pObject->MemberFunction();
// no need to delete pObject
}
注意:auto_ptr仅在使用delete释放资源时使用。
25.使用异常处理更简单,更可靠,更有效,可以创建更健壮的代码。然而,你应该只在意外的情况下使用异常处理。如果你认为一个指针应该时空值,这种条件下就直接在代码中检查这个值,而不要使用异常。
26.非MFC的C++异常应该通过引用来扑获。使用引用扑获异常不需要删除异常对象(因为使用引用扑获的异常会在栈中传送),而且它保留了多态性(因此你扑获的异常对象正是你抛出的异常对象)。使用指针扑获异常需要你删除对象,而使用值扑获对象会导致对象的“分片”(slicing),也就是说,将派生的异常对象转化为扑获的数据类型。
27.MFC异常应该通过指针来扑获。因为它们通常从堆中分配,当你处理完异常之后,你需要调用Delete成员函数:
...
catch (CFileException *e) {
// handle file exception
...
e->Delete(); // required to prevent a memory leak
}
因此,你不可以使用省略扑获处理器扑获MFC异常,因为者会导致一个内存泄漏。你必须使用Delete成员函数删除MFC异常,而不要用delete操作符,因为一些MFC异常作为静态对象创建。
28.一旦扑获了异常,你可以通过执行下列典型动作的一些组合来处理它:
(1) 什么也不要做。
(2) 修改这个问题并重试代码。
(3) 修改这个问题但不要重试代码。
(4) 如果用户需要的话,向用户显示错误信息。
(5) 如果出现的问题不是程序错误的话,输出一个诊断的跟踪消息。
(6) 如果出现的问题是程序错误,输出一个断言。
(7) 在日志文件中记录这个问题。
(8) 如果异常是不可恢复的,停止进程的运行。
(9) 整理已分配的资源。
(10) 重新抛出这个异常,使得高级函数也能处理这个异常,特别是在当前函数不能完全解决的情况下。你可以重新抛出同一个异常对象,或抛出一个新的异常对象。
29.和/EHa相对的是同步异常(/EHs),而不是/GX。/GX实际上是/EHsc的简化形式。/GX表示编译器应该假设extern "C" 的函数不抛出C++异常,而/EHs则抛出。
30.为用户和调用环境记录异常。通常,异常对象类型用于向调用环境通知出现的问题,而问题的描述字符串用于向用户通知。
31.Visual C++的默认情况下,new和malloc对于错误不会抛出异常,但你可以通过使用_set_new_handler安装一个处理器,让new针对错误抛出异常。你也可以让malloc通过调用_set_new_mode使用同一处理器。
#include <new.h>
class bad_alloc : public exception {
public:
bad_alloc(const __exString& what_arg) : exception (what_arg) {}
};
int NewHandler (size_t size) {
throw bad_alloc("Operator new couldn't allocate memory");
return 0;
}
int APIENTRY WinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow) {
_set_new_handler(NewHandler);
_set_new_mode(1); // use NewHandler for malloc as well
...
}
32.如果已存在的代码中没有设定new返回空值,那么你应该始终让new出错时抛出一个异常。
33.浮点数和整数不一样,在默认情况下它被零除不会出现异常,但是会出现一个非常奇怪的值"1.#INFO" (它表示这个值并不是一个数字)。要让检测浮点数问题更简单一些,你应该用如下的代码让浮点数错误抛出异常。
#include <float.h>
int cw=controlfp(0,0);
cw &= ~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID);
_controlfp(cw, MCW_EM);
浮点数异常处理器必须调用_clearfp作为它的第一条指令来清空浮点数异常。