异常是指程序在运行过程当中产生可预料的执行分支。如除0操做,数组访问越界、要打开的文件不存在。
Bug是指程序中的错误,是不被预期的运行方式。如野指针、堆空间使用结束未释放。
C语言中处理异常的方式通常是使用if....else...分支语句。ios
double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { cout << "a is devieded by zero" <<endl; } return ret; }
C语言经过setjmp和longjmp对异常处理进行优化。int setjmp(jmp_buf env);
将上下文保存到jmp_buf结构体中void longjmp(jmp_buf env, int value);
从jmp_buf结构体中恢复setjmp保存的上下文,最终从setjmp函数调用点返回,返回值为value。数组
#include <iostream> #include <csetjmp> using namespace std; static jmp_buf env; double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { longjmp(env, 1); } return ret; } int main(int argc, char *argv[]) { if( setjmp(env) == 0 ) { double r = divide(1, 1); cout << "r = " << r << endl; } else { cout << "Divided by zero..." << endl; } return 0; }
C++语言中内置了异常处理的语法,try.....catch......。
try语句块用来处理正常代码逻辑,catch语句块用来处理异常处理状况,throw抛出异常。在try语句块抛出的异常在相应的catch语句块捕获处理。
同一个try语句块能够对应多个catch语句块,catch语句块能够定义具体处理的异常类型,不一样的类型的异常由不一样的catch语句块处理,try语句块能够抛出任何类型的异常,catch(...)用于处理全部类型的异常,任何异常都只能被捕获一次。
throw抛出的异常必须被catch处理,若是当前函数可以处理异常,继续执行;若是当前函数不能处理异常,函数中止执行并返回。未被处理的异常会顺着函数调用栈向上传递,直到被处理为止,不然程序将中止执行。
异常处理的匹配:
A、异常抛出后从上到下严格匹配每一个catch语句块处理的类型,不能进行任何类型转换。
B、catch(...)语句块只能放到catch语句块分支的最后位置。
异常处理的使用实例:ide
try { throw 'c'; } catch(char c) { cout << "catch(char c)" << endl; } catch(short c) { cout << "catch(short c)" << endl; } catch(double c) { cout << "catch(double c)" << endl; } catch(...) { cout << "catch(...)" << endl; }
catch语句块捕获的异常从新解释后能够抛出异常,抛出的异常在外层的try...catch中捕获。函数
try { try { throw 'c'; } catch(int i) { cout << "Inner: catch(int i)" << endl; throw i; } catch(...) { cout << "Inner: catch(...)" << endl; throw; } } catch(...) { cout << "Outer: catch(...)" << endl; }
一般在catch语句块中捕获的异常从新解释后能够再次抛出异常,工程实践中一般用于统一异常类型,如经过捕获第三方库函数中抛出的异常,从新解释后抛出统一的异常处理信息。
异常的类型能够是自定义类型,自定义类型的异常匹配依旧是自上而下严格匹配,但因为赋值兼容性原则在异常匹配中适用,因此匹配子类异常的catch语句块放在catch分支的上部,匹配父类异常的catch语句块放在catch分支的下部。学习
#include <iostream> using namespace std; class Parent { public: Parent(int i):code(i) { } private: int code; }; class Child : public Parent { public: Child(int i):Parent(i),code(i) { } private: int code; }; int main(int argc, char *argv[]) { try { Child child(1); throw child; } catch(const Child& e) { cout << "catch(const Child& e)" << endl; } catch(const Parent& e) { cout << "catch(const Parent& e)" << endl; } return 0; }
STL提供了实用的异常处理类,STL中的异常都是从exception类继承而来,exception类只要有两个分支,logic_error和runtime_error。logic_error用于处理程序中可避免逻辑错误,runtime_error用于处理程序中没法处理的恶性错误。优化
#include <iostream> #include <stdexcept> using namespace std; int main(int argc, char *argv[]) { int array[5] = {0}; for(int i = 0; i < 5; i++) { array[i] = i; } try { for(int i = 0; i < 10; i++) { if(i >= 5) { throw out_of_range("out of range"); } else { cout << array[i] <<endl; } } } catch(const out_of_range& e) { cout << e.what() << endl; } return 0; }
try...catch语句用于分隔正常功能代码与异常处理代码。try...catch语句也能够将函数体分隔为两部分。
函数声明和定义时能够直接指定可能抛出的异常类型,异常声明做为函数的一部分能够提升代码可读性。
函数异常声明是一种与编译器之间的契约,函数声明异常后就只能抛出声明的异常。若是抛出其它异常将会致使程序运行终止。也能够经过函数异常声明定义无异常函数。spa
#include <iostream> using namespace std; //声明抛出的异常类型为int void func(int i, int j)throw(int) { if(0 < j && j < 10) { } else { throw 0; } } void test(int i)try { func(i,i); } catch(int i) { cout << "catch(int i): " << i << endl; } catch(...) { cout << "Exception:" << endl; } int main(int argc, char *argv[]) { test(10); test(1); return 0; }
上述代码中,func函数声明了抛出的异常类型为int,所以func函数只能抛出int类型异常,若是抛出其它类型异常将致使程序运行终止。即便test函数能够对抛出的其它类型异常进行捕获,程序也会运行终止。
若是函数内部可能会抛出多种类型的异常,须要在函数声明异常时指定声明的异常类型,代码以下:指针
#include <iostream> #include <string> using namespace std; //声明抛出的异常类型为int,char,string void func(int i, int j)throw(int,char,string) { if(0 < j && j < 10) { throw j; } if(10 < j && j < 100) { throw 'A'; } else { throw string("string exception."); } } void test(int i)try { func(i,i); } catch(int i) { cout << "catch(int i): " << i << endl; } catch(char c) { cout << "Exception:" << c << endl; } catch(string s) { cout << s << endl; } catch(...) { cout << "Exception:" << endl; } int main(int argc, char *argv[]) { test(115);//string exception. test(1);//catch(int i): 1 test(20);//Exception:A return 0; }
上述代码中,func函数能够抛出多种类型的异常,test函数会捕获func函数抛出的多种异常类型。rest
若是异常没有被处理,terminate函数会被自动调用。terminate函数是整个程序释放系统资源的最后机会。默认状况下,terminate函数调用abort库函数终止程序。abort函数使得程序执行异常而当即退出。code
#include <iostream> using namespace std; class Test { public: Test() { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } }; int main(int argc, char *argv[]) { static Test test; throw 1; return 0; }
上述代码运行结果以下:
C++支持使用自定义的terminate函数实现替换默认的terminate函数实现。
自定义terminate函数的实现规则以下:
A、自定义一个无返回值、无参数的函数
B、不能抛出任何异常
C、必须以某种方式结束当前程序
经过调用set_terminate函数能够设置自定义的terminate结束函数,其用法以下:
A、参数类型为void (*)()
B、返回值为默认的terminate函数入口地址
#include <iostream> #include <cstdlib> using namespace std; class Test { public: Test() { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } }; void terminate_test() { cout << "void terminate_test()" << endl; exit(1); } int main(int argc, char *argv[]) { set_terminate(terminate_test); static Test test; throw 1; return 0; } // output: // Test() // void terminate_test() // ~Test()
上述代码在最终terminate_test结束函数中调用了exit(1),exit函数会确保程序中全局、静态数据区的对象被正确销毁。若是使用abort函数替换exit函数,程序运行结果以下:
析构函数中抛出异常可能会致使最终结束函数terminate函数会被重复调用。
C++语言提供用于声明函数抛出异常的语法声明。异常声明做为函数声明的修饰符,位于函数参数表的后面。函数异常声明的示例以下:
//可能抛出任何异常 void func1(); //只能抛出的异常类型:char,int void func2() throw(char, int); //不抛出任何异常 void func3() throw();
函数异常声明的意义以下:
A、提示函数调用者必须作好异常处理的准备
B、提示函数的维护者不要抛出其它异常
C、函数异常规格说明是函数接口的一部分
若是函数抛出的异常类型不在函数异常声明中,全局unexpected()函数会被调用。默认的unexpected()函数会调用全局的terminate函数,能够自定义函数替换默认的unexpected()函数实现。
自定义的unexpected()函数的实现规则以下:
A、自定义一个无返回值、无参数的函数
B、可以再次抛出异常,当异常符合触发函数的异常规格说明时,恢复程序执行。不然,调用全局terminate函数结束程序。
经过调用set_unexpected函数能够设置自定义的unexpected()函数,用法以下:
A、参数类型为void (*)()
B、返回值为默认的unexpected()函数入口地址。
#include <iostream> #include <cstdlib> using namespace std; void func() throw(int) { cout << "void func()throw(int)" << endl; throw 'A'; } void unexpected_test() { cout << "void unexpected_test()" << endl; throw 1; } int main(int argc, char *argv[]) { set_unexpected(unexpected_test); try { func(); } catch(int) { cout << "catch(int)" << endl; } catch(char) { cout << "catch(char)" << endl; } return 0; } // output: // void func()throw(int) // void unexpected_test() // catch(int)
C++编译器不必定对C++语言中函数异常规格说明进行支持。VC++编译器不支持,G++编译器支持。
C语言中,malloc函数申请内存失败时返回NULL值。
C++语言中,对于早期的C++编译器,new关键字申请内存失败时,返回NULL值;对于现代C++编译器,new关键字申请内存失败时,抛出std::bad_alloc异常。
C++语言规范中,new关键字的标准行为以下:
A、new在内存分配时,若是空间不足,会调用全局的new_handler函数,new_handler函数中抛出std::bad_alloc异常;若是成功,会在分配的空间调用构造函数建立对象,并返回对象的地址。
B、能够自定义new_handler函数,处理默认new内存分配失败的状况。
#include <iostream> #include <cstdlib> using namespace std; void new_handler_test() { cout << "void new_handler_test()" << endl; cout << "No enough memory" << endl; exit(1); } int main(int argc, char *argv[]) { set_new_handler(new_handler_test); int* p = new(std::nothrow) int[10000000000]; return 0; } // output: // void new_handler_test() // No enough memory
上述代码中,自定义new_handler函数,抛出异常时会调用。
#include <iostream> #include <cstdlib> #include <new> using namespace std; void new_handler_test() { cout << "void new_handler_test()" << endl; cout << "No enough memory" << endl; exit(1); } int main(int argc, char *argv[]) { new_handler func = set_new_handler(new_handler_test); cout << "func = " << func << endl; if(func) { try { func(); } catch(const bad_alloc& e) { cout << e.what() << endl; } } return 0; } // func = 0
上述代码是在G++编译器、VC++编译器下编译执行后打印的结果,代表G++编译器、VC++编译器没有设置默认的new_handler函数。若是C++编译器(如BCC编译器)设置有默认的new_handler函数,func函数执行时将会抛出bad_alloc异常,被捕获后打印出bad_alloc异常的相关信息。
不一样的C++编译器,new关键字申请动态内存失败时表现不一样。
工程实践中,为了在不一样C++编译器间统一new关键字的行为,提升代码的可移植性,解决方案以下:
A、从新定义全局的new/delete实现,不抛出任何异常;自定义new_handler函数,不抛出任何异常(不推荐)。
B、在类内重载new/delete操做符,不抛出任何异常。
C、单次动态内存分配时使用nothrow参数,指明new不抛出异常。
#include <iostream> #include <cstdlib> #include <new> using namespace std; class Test { int m_data; public: Test() { cout << "Test()" << endl; m_data = 0;//异常 } ~Test() { cout << "~Test()" << endl; } void* operator new (unsigned int size) { cout << "operator new: " << size << endl; // return malloc(size); return NULL; } void operator delete (void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[] (unsigned int size) { cout << "operator new[]: " << size << endl; // return malloc(size); return NULL; } void operator delete[] (void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; int main(int argc, char *argv[]) { Test* p = new Test(); cout << p << endl; delete p; return 0; } // output: // operator new: 4 // Test() // 异常
上述代码在执行new操做符函数后会调用Test构造函数,并在初始化m_data成员变量时抛出异常。为了确保不一样C++编译器在调用new关键字时具备相同的行为,须要在new失败时不抛出异常,所以须要在new操做符增长函数的异常声明。
#include <iostream> #include <cstdlib> #include <new> using namespace std; class Test { int m_data; public: Test() { cout << "Test()" << endl; m_data = 0;//异常 } ~Test() { cout << "~Test()" << endl; } void* operator new (unsigned int size) throw() { cout << "operator new: " << size << endl; // return malloc(size); return NULL; } void operator delete (void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[] (unsigned int size) throw() { cout << "operator new[]: " << size << endl; // return malloc(size); return NULL; } void operator delete[] (void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; int main(int argc, char *argv[]) { Test* p = new Test(); cout << p << endl; delete p; p = new Test[5]; cout << p << endl; delete [] p; return 0; } // output: // operator new: 4 // 0 // operator new[]: 24 // 0
上述代码对Test类的new和delete关键字进行了重载,统一了new失败时的行为。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { //不抛出异常 int* p = new(nothrow) int[1000000000]; cout << "p = " << p << endl; delete [] p; int array[2] = {0}; struct Test { int x; int y; }; //在栈空间建立对象 Test* pTest = new(array) Test(); pTest->x = 100; pTest->y = 200; cout << array[0] << endl; cout << array[1] << endl; //显示析构 pTest->~Test(); return 0; } // output: // p = 0 // 100 // 200
上述代码中使用nothrow关键字对象new进行限制,确保new建立对象失败时不会抛出异常。new关键字也能够指定建立对象的地址空间,好比栈空间。
不是全部的C++编译器都遵循C++标准规范,C++编译器可能从新定义new关键字的实现,并在实现中抛出bad_alloc异常。VC++编译器对new关键字进行了重定义,new关键字在new.cpp文件中进行了实现。
#ifdef _SYSCRT #include <cruntime.h> #include <crtdbg.h> #include <malloc.h> #include <new.h> #include <stdlib.h> #include <winheap.h> #include <rtcsup.h> #include <internal.h> void * operator new( size_t cb ) { void *res; for (;;) { // allocate memory block res = _heap_alloc(cb); // if successful allocation, return pointer to memory if (res) break; // call installed new handler if (!_callnewh(cb)) break; // new handler was successful -- try to allocate again } RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0)); return res; } #else /* _SYSCRT */ #include <cstdlib> #include <new> _C_LIB_DECL int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc); _END_C_LIB_DECL void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } /* * Copyright (c) 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED. * Consult your license regarding permissions and restrictions. V3.13:0009 */ #endif /* _SYSCRT */
上述代码显示,在new失败时默认抛出bad_alloc异常。