文章目录
- 一.C++对运行时错误的处理方式
- 函数调用链中的异常机制
- 二.异常的使用规范
- 三.C++异常体系
- C++标准库中的异常体系
- 四.关于C++异常的注意事项
一.C++对运行时错误的处理方式
- 传统的C语言处理运行时错误采用的是assert或者错误码的方式,这种异常处理机制对错误信息的定位和描述能力都十分有限,不能满足面向对象编程的需求,C++对运行时异常的处理方式是将错误信息封装在对象中==,通过
throw
和catch
的方式进行传递和处理- 关键字
try
:try作用域中的代码段受异常机制保护(必须和catch
关键字配对) - 关键字
catch
:与try关键字配对(一个try可以匹配多个catch),用于接收try作用域代码段中特定类型的异常对象,在catch
作用域中可以对异常进行处理 - 关键字
throw
:代码段中用于抛出异常对象的关键字
- 关键字
#include <iostream>
#include <string>
using namespace std;
//用于封装错误信息的类
class Object
{
public:
//异常对象的构造
Object(const char * str = "")
: _msg(str)
{}
private:
string _msg;
};
void function(bool judge)
{
if (judge)
{
//满足某种条件,抛出临时构造的异常对象
throw Object("程序异常");
}
}
int main()
{
try
{
//try作用域中的代码块受异常机制保护
function(true);
}
catch (Object e1)//Object表示类的类型名,e1类似于函数形参(catch机制类似于函数调用)
{
cout << "捕获Object类型的异常" << endl;
//处理异常
}
catch (...) //...可以捕获任意类型的异常
{
cout << "捕获未知异常" << endl;
//处理异常
}
return 0;
}
catch
关键字的参数和throw
抛出的对象类型必须是相匹配的,此时异常对象才能被catch
接收(catch(...)
可以接受任意类型的异常对象)(catch
机制类似于函数调用)- 体系中有多个
try
和catch
关键字组时,抛出的异常会根据对象类型匹配离throw
语句位置最近的catch
语句. throw
抛出的异常必须被接收,否则程序就会直接终止throw
抛出异常后,运行的程序将直接跳转到相应的catch
作用域中,throw
语句之后的代码段便无法被执行,要尤其注意这种代码跳转机制可能引发的内存泄漏问题(比如delete
语句被跳过)
函数调用链中的异常机制
二.异常的使用规范
异常如果抛出后没有被接收,程序就会终止,因此函数的设计者应该向调用者声明函数是否能够抛出异常以及能够抛出什么类型的异常
void func() throw(TypeA,TypeB,TypeC,TypeD);
表示函数func能够抛出TypeA,TypeB,TypeC,TypeD四种类型的异常对象void func() noexcept
表示函数不会抛出任何异常
三.C++异常体系
- 为了让异常体系规范化和标准化,各异常类通常设计成一个继承体系,所有的异常类都继承自同一个父类并且让异常类重写父类的虚函数,将父类的引用作为
catch
的参数类型,就可以实现对所有子类异常对象的捕捉(父类和子类对象的赋值兼容),再通过多态机制就可以调用各个子类异常对象的功能接口:
#include <iostream>
#include <string>
#include <thread>
using namespace std;
// 服务器开发中通常使用的异常继承体系
//基类Exception
class Exception
{
public:
Exception(const string& errmsg, int id)
:_errmsg(errmsg)
, _id(id)
{}
virtual string what() const
{
return _errmsg;
}
protected:
string _errmsg;
int _id;
};
//子类SqlException
class SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
:Exception(errmsg, id)
, _sql(sql)
{}
//重写父类虚函数
virtual string what() const override
{
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
//子类CacheException
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
//重写父类虚函数
virtual string what() const override
{
string str = "CacheException:";
str += _errmsg;
return str;
}
};
//子类HttpServerException
class HttpServerException : public Exception
{
public:
HttpServerException(const string& errmsg, int id, const string& type)
:Exception(errmsg, id)
, _type(type)
{}
//重写父类虚函数
virtual string what() const override
{
string str = "HttpServerException:";
str += _type;
str += ":";
str += _errmsg;
return str;
}
private:
const string _type;
};
- 测试上面的异常体系:
//功能接口
void SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
}
//功能接口
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
SQLMgr();
}
//功能接口
void HttpServer()
{
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1));
try
{
HttpServer();
}
catch (const Exception& e) // 这里使用父类引用实现子类对象的捕捉
{
//多态调用
cout << e.what() << endl;
}
catch (...)
{
//捕捉到未知异常
cout << "Unkown Exception" << endl;
}
}
return 0;
}
C++标准库中的异常体系
- 一般来说,企业会开发出一套自己的异常体系来适应实际应用的需求
四.关于C++异常的注意事项
- 异常的抛出和捕获会导致程序的执行流发生跳转,容易导致内存泄漏、死锁等异常安全问题.
- 异常体系的设计要遵循规范:
- 一、各个异常类型都继承自一个基类。
- 二、函数是否抛异常,抛什么类型的异常,都要向调用者声明.
- 尽量不要在对象的构造函数和析构函数中抛出异常,避免对象的初始化和资源清理无法正常进行.