传统的错误处理机制:
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛异常,让函数直接或者间接的调用者处理这个错误.
使用try/catch语句的语法如下:
try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
catch( ... ) //接收任意类型的异常.
{
}
在调用链中异常栈展开匹配原则:
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg) //接收最符合抛出对象类型
{
cout << errmsg << endl;
}
catch (...) //接收任意类型的异常.
{
cout << "unkown exception" << endl;
}
return 0;
}
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者就要写很多种catch来匹配对应的异常.所以在实际中都会定义一套继承的规范体.这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了.此时,该catch中,异常对象e使用的虚函数为派生类中重写的虚函数.(构成多态).
例如:
以下代码中,写了一个异常类Exception(基类),以及一个HttpServerException类(派生类).在抛异常时抛出的对象为派生类,捕获的类型为基类.
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)
{
Sleep(1);
try {
HttpServer();
}
catch (const Exception& e) // 使用基类类型捕获.
{
// 多态
cout << e.what() << endl;
}
catch (...) //捕获任意类型对象.
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
注意:
为了让代码更加规范,在公司中我们一般抛出的类型为类,我们可以通过带调用类的成员函数来处理相应异常问题.
class Exception
{
public:
Exception(const string& errmsg, int id)
:_errmsg(errmsg)
, _errid(id)
{}
virtual string what() const
{
return _errmsg;
}
int GetId() const
{
return _errid;
}
protected:
string _errmsg;
int _errid;
};
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 SeedMsg( const string& str )
{
//要求出现网络错误,重试三次.
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("网络错误", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
cout << "发送成功:" << str << endl;
}
void HttpSever()
{
//如果出现网络错误,重试三次.
string str("今晚一起去看电影");
int n = 3;
while ( n-- )
{
try
{
SeedMsg(str);
//如果没有异常,则直接退出.
break;
}
catch (const Exception& e) //可以接收权限导致的异常和网络错误导致的异常,我们要分开进行处理.
{
//网络错误,且重试3次内.
if (e.GetId() == 100 && n > 0 )
{
continue;
}
else
{
//异常的重新抛出.
throw e; //1.为了防止权限错误全被该catch所捕获,导致main函数里面的catch无法被捕获,所以我们需重新抛出权限错误所导致的异常.
//2.为了由main函数里面的catch接受网络错误且重试等于三次时的异常,此时也可以重新抛出异常,由main函数里面的catch接收,打印相应字符串.
}
}
}
}
int main()
{
while(1)
{
try
{
HttpSever();
}
catch ( const Exception& e ) //什么派生类抛出,就分别调用什么.
{
cout << e.what() << endl;
}
catch ( ... )
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
总结:
异常一般由main函数中的catch所处理,中间中的catch一般根据问题类型,分别处理后,再重新抛出,由main函数中的catch处理.
double Division(int a, int b) //表示该函数不会抛异常,但是不会强制.
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int* array1 = new int[10];
int* array2 = new int[10];
int len, time;
cin >> len >> time;
try
{
cout << Division(len, time) << endl;
}
catch (...) //捕获Division中抛出的异常.
{
cout << " delete [] array1 " << endl;
delete[] array1;
delete[] array2;
throw; //重新抛出.
}
}
int main()
{
try
{
Func();
}
catch ( const char* errmsg )
{
cout << errmsg << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
return 0;
}
可是,这样写还是有一个隐患:
当第一个new成功,第二个new失败时,此时就会抛出异常,就会由main函数中的catch捕捉,此时,array1没有及时被销毁,又造成了内存泄露.
解决办法如下:
我们可以将有可能发生异常的(new array2)放入异常捕获范围中(try中),当new array2抛出异常,可由当前栈帧catch(…)捕获,再将array1销毁,再重新抛出.
double Division(int a, int b) //表示该函数不会抛异常,但是不会强制.
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int* array1 = new int[10];
int len, time;
cin >> len >> time;
try
{
int* array2 = new int[10];
cout << Division(len, time) << endl;
}
//不去区分异常,抛出任何类型的异常都进行捕获,但是处理一般放在main函数的catch中处理.
catch (...)
{
cout << " delete [] array1 " << endl;
delete[] array1;
throw;
}
}
int main()
{
try
{
Func();
}
catch ( const char* errmsg )
{
cout << errmsg << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
return 0;
}
特殊说明:
这样处理固然可以解决array1造成的内存泄露,可是,如果我new了多个数组,这些数组都有可能创建失败,抛出异常,这样就会造成多种情况的内存泄漏,那么针对不同情况,又要分开处理.这样会导致代码过于繁琐.因此实际上一般不用异常处理,这在我们学习的智能指针可以解决.
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常,如果发生了异常,则会终止程序.
thread() noexcept;
thread (thread&& x) noexcept;
异常的优点:
异常的缺点: