错误处理我们的程序有时会遇到运行问题,我们必需保护程序能够正常运行。一个程序可能需要更多的内存或是打开一个不存在的文件或是遇到一个不能处理的错误。通常有下面三种方式来进行错误处理:1. 终止程序.2. 返回一个错误码3. 抛出一个异常我们应该避免第一种方式虽然有很多调用 abort()或exit()操作的例子。在2与3之间我们不需要有太多争议哪种方式更好点。我让我通过简单的代码来进行比较:下面是通过使用错误代码方式来确定一个函数的成功与失败:if(object1.func() == ERROR) ErrorHandle();if(object2.func() == ERROR) ErrorHandle();if(object3.func() == ERROR) ErrorHandle();但我们也能抛出一个对象的错误代码,然后在捕获:try{ object1.func(); object2.func(); object3.func();}catch (const std::exception &e){ ErrorHandle();}异常处理与返回错误进行比较 返回错误的方式是每个函数的一种非常简单并且非常健壮的返回错误的方式。 异常处理方式我们从正常的流程控制分开了错误处理代码,使代码更加可读。我们能捕获一个或多个错误不需要检测每个函数的返回值。我们能一个回调中处理错误而不是在发生错误点。我们能够得到更多的错误信息而不是一个单独的错误码。异常处理是唯一种能够在构造函数中报告错误的方式。在运行中处理异常是种昂贵的栈操作。没有捕获的异常可能导致程序退出或数据丢失。写异常安全的代码是比较困难的,如果没有正确处理可能会导致资源泄露。使用异常操作应该是要不就全用,要不就不用。如果一部分代码使用异常操作那么整个代码都要用异常操作。abort()与exit()abort()函数的原型在cstdlib头文件中。如果此函数被调用,如果此函数被调用其会发送一个类似不正常结束程序到标准库的错误流中并结束程序的运行.它也可能返回一个独立于实现方式的值来告诉操作系统错误。abort()是否处理缓冲区取决于实现.所以如果你喜欢你可以用exit来处理缓冲区.但是exit没有给我任何信息,请看下面的例子。注意调用从myCalculator函数中调用abort()直接结束程序的运行没有返回到main()函数中.
#include <iostream>
#include <cstdlib>
double myCalculator(double a, double b) {
if(a == b) {
std::cout << "abort()!" << std::endl;
abort();
}
return 1 / (a - b);
}
int main () {
double x,y,z;
x = 10;
y = 10;
z = myCalculator(x,y);
std::cout << "x = " << x << " y = " << y << " z = " << z << std::endl;
return 0;
}
输出结果是:
abort()
如果把上面代码中的abort()换成exit(1)则输出结果是:
exit(1)
try 与 catch
c++ 异常处理是为应对当一个正常运行的程序出现异常情况的一种应对方式。异常处理为我们提供了一种方式可以将程序控制流从一部分转到另一部分.这样我们就可以分开程序正常流程与错误处理流程,使我们的代码更加易读。
为了捕获这种异常我们必需在可能发生异常的部分加上检测代码。这项工作可以通过try的代码块完成.当有异常状态在那部分代码块触发时,一个异常行为将被抛出并将代码流程转到异常处理部分.如果没有异常抛出,这块代码继续执行所有的处理将被忽略.
异常处理有三部分组成:
抛出一个异常
捕获一个句柄的异常
使用try语句块
throw语句就是一个跳转代码.换句话说就是告诉程序将代码跳转到其它部分.并传一个错误字符符或一个错误值来标识异常信息.
请看下面处理代码:
#include <iostream>
using namespace std;
int main () {
try {
throw "Homemade exception";
// Because the function throws an exception,
// the rest of the code in this block will not be executed
...
...
}
// Execution will resume here
catch (const char* e) {
cout << "exception: " << e << endl;
}
return 0;
}
输出结果为:
exception: Homemade exception
try 语句块只有异常处理语句.仅仅抛出一个异常:
throw "Homemade exception";
throw 接收一个参数做为异常处理的参数.
catch 关键字是为声明异常处理的,其紧跟随try语句块.catch句语块的格式与普通的函数非常像必需要有一个参数。这个参数的类型也是非常重要的,因为这个参数类型是对就throw表达式的,catch部分只处理匹配的参数.
多个异常处理也是经常被用到的,每个都有一个不同的参数类型.只有与抛出的类型相匹配的参数才会被执行.
省略号(…)可以被当成catch的一个参数.这个句柄可以捕获任何throw抛出的异常.所以它可以被用作任何默认的参数来捕获最后没有被处理的类型:
#include <iostream>
using namespace std;
int main () {
try {
throw "Homemade exception";
}
catch (int e){
cout << "int exception: " << e << endl;
}
catch (char e){
cout << "char exception: " << e << endl;
}
catch (...) {
cout << "default exception ";
}
return 0;
}
在这个例子中,异常处理运行于:
catch(…)
这个异常处理也能够捕获任何参数类型为 int或char的异常行为。
当异常处理完后以后程序继续执行try-catch后面的代码。如果有没有捕获的异常行为最终可能导致异常程序退出。
在c++中可以存在嵌套异常处理行为不过需要额外的try语句块.在这种情况下,我们有可能有一个内部的catch语句块继续向外层抛出.这个工作由没有参数的throw完成.
下面是示例代码:
#include <iostream>
using namespace std;
int main () {
try {
try {
throw 911;
}
catch (int n) {
cout << "inner catch " << endl;
throw;
}
}
catch (...) {
cout << "catching any exception occurred" << endl;
}
return 0;
}
输出结果如下:
inner catch
catching any exception occurred
特殊异常规范
我们能够直接或间接限制异常类型通过在函数声明后面抛出一个后缀:
float f(char param) throw (int);
这样就声明了一个函数f(),这个函数有一个char类型的变量并且皇家马德里回一个float类型的值.唯一不同的是这个函数可能抛出一个类型为int的值.如果抛出不同的类型的异常,它不可能被int类型的捕获.
如果抛出一个左侧为空的异常,这就说明此函数不允许抛出异常.如果像普通函数一样没有特别说明则此函数可以抛出任何类型的异常:
int f(int param) throw(); // no exceptionsallowed
int f(int param); // all exceptions allowed
标准异常操作
C++ 标准库提供了一个基类来声明一个对象抛出异常.这个类叫做exeception定义在<exception>头文件中.这个类有通常的构造函数,拷贝构造函数及析构函数,其还有一个额外的虚函数what()返回一个非空的string:
public exception {
public:
virtual const char* what() const throw();
};
这个字符串的内容根据不同的实现而不同.其被日志帮助信息的详细程度而不同.
#include <string>
#include <iostream>
using namespace std ;
int main()
{
string s = "test me" ;
try
{
s[8] = '!';
}
catch( exception &error )
{
cerr << "Exception: " << error.what() << endl ;
}
return 0 ;
}
如果我们运行这个程序我们得到:“String subscript out of range”信息.
这个what()函数可以在子类中重写来包括详细的异常信息
#include <iostream>
#include <exception>
using namespace std;
class child_exception: public exception {
virtual const char* what() const throw(){
return "child exception occurred";
}
};
int main () {
try {
child_exception cex;
throw cex;
}
catch (exception& e) {
cout << e.what() << endl;
}
return 0;
}
所有的c++的异常处理标准备库中的异常操作都是继承自std::exeption类.
bad_aloc
bad_cast
bad_exeption
写线程安全的代码
写异常安全的代码也是比较困难的,所以我们应该只在我们需要的时候才用异常操作.
下面有几个异常安全代码的条款by H. Sutter
越少用 try和catch越好
最好通过析构函数来自动处理资源释放而不是通过try/catch
做一个比较全面的错误处理策略并且严格的去执行
通过throw来发现错误而不是其自己处理.
写try与catch的地方需要有全面的异常知识来处理错误,转换或强迫边界定义的策略