前言
大家好久不见,今天一起来学习一下c++中的异常。
C语言C++对比
C语言在处理错误的时候一般使用assert或者错误码来处理,但二者都相对有局限性
assert:错误自动终止程序,处理方式非常暴力
错误码:只有一个错误码,错误信息相对少,不直观
C++语言提供了一种新的方式解决上面的问题,即异常
异常
throw在可能出现异常的地方使用,意为抛出异常;try/catch在处理异常的地方使用,意为处理异常。
实例代码:
#include <iostream>
using namespace std;
double Div()
{
int x, y;
cin >> x >> y;
if (y == 0)
throw "div Exception cause div 0!!!";
else
return (double)x / (double)y;
}
int main()
{
try
{
Div();
}
catch (const char* str)
{
cout << "[Exception]:" << str << endl;
}
return 0;
}
捕获异常时的注意事项
匹配
1、类型必须匹配,必须严格匹配。
2、在最近的捕获处抛出。
第二点:比如在此处,抛出时离middle最近且严格匹配,所以直接在middle层抛出了。
#include <iostream>
using namespace std;
double Div()
{
int x, y;
cin >> x >> y;
if (y == 0)
throw "div Exception cause div 0!!!";
else
return (double)x / (double)y;
}
void middle()
{
try
{
Div();
}
catch (const char* str)
{
cout << "[middle-Exception]:" << str << endl;
}
}
int main()
{
try
{
middle();
}
catch (const char* str)
{
cout << "[main-Exception]:" << str << endl;
}
return 0;
}
细节
异常抛出的时候相当于传值返回,因此捕获到的是异常的一份拷贝构造,因为编译器有很多优化,所以在抛出异常的时候一般都直接抛出一个匿名对象,捕获到的异常是一个局部对象,而不是一个临时对象这点要分清楚。
使用一个Exception类丰富错误信息
使用字符串来表示异常也有很多局限,一般在存储异常信息的时候都是使用一个异常类:
现在我们模拟一个场景:在一个服务中,我们提供网络服务、数据库服务,如果数据库出现错误要看一下特定的sql语句。
结合继承和多态,我们能够设计出更符合实际需求的异常代码:这样也会减少乱抛异常导致系统混乱的可能。顺便一提,catch里加…表示可以捕获任意异常,尽管我们无从得知是什么异常,但可以保证程序其他功能不受限制。
class Exception
{
public:
Exception(int errid,string errmsg)
: _errid(errid)
, _errmsg(errmsg)
{
}
virtual string GetErrMsg() const
{
return _errmsg;
}
int GetErrid() const
{
return _errid;
}
protected:
int _errid;
string _errmsg;
};
class SqlException : public Exception
{
public:
SqlException(int errid, const string& errmsg,const string& sql)
: Exception(errid,errmsg)
, _sql(sql)
{
}
virtual string GetErrMsg() const override
{
string msg = "SqlException:";
msg += _errid;
msg += "->";
msg += _sql;
return msg;
}
private:
string _sql;
};
class CacheException : public Exception
{
public:
CacheException(int errid, const string& errmsg)
: Exception(errid,errmsg)
{
}
virtual string GetErrMsg() const override
{
string msg = "CacheException:";
msg += _errmsg;
return msg;
}
};
class HttpServerException : public Exception
{
public:
HttpServerException(int errid, const string& errmsg, const string& type)
: Exception(errid, errmsg)
, _type(type)
{
}
virtual string GetErrMsg() const override
{
string msg = "HttpServerException:";
msg += "type->";
msg += _type;
msg += _errmsg;
return msg;
}
private:
const string _type;
};
void SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException(100, "权限不足", "select * from name = '张三'");
}
cout << "调用成功" << endl;
}
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.GetErrMsg() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
异常的重新抛出
在这样的场景下:假如在func函数中要记录下来每一次div的结果,但如果发现除0错误后会引发异常,导致动态内存分配的空间没有释放,因此需要在这一层捕获异常并释放空间,但我么又希望异常能在外层统一处理,所以引入了重新抛出异常的概念,代码如下:
double Div()
{
int x, y;
cin >> x >> y;
if (y == 0)
throw "div Exception cause div 0!!!";
else
return (double)x / (double)y;
}
void func()
{
int* arr = new int[20];
try
{
Div();
}
catch (const char* str)
{
delete[] arr;
throw;
}
delete[] arr;
}
int main()
{
try
{
func();
}
catch (const char* str)
{
cout << str << endl;
}
}
c++98和c++11的异常建议
在c++98中,提供了指明异常的方式,但这种方式其实非常没有用,形同虚设,因为你在抛出异常的时候可以不采用这种方式,就算采用这种方式,也会有一些其他问题。
throw([e1],[e2]) 表示可能会抛出的异常
比如:
说明不抛出异常的类仍然可以抛出并且程序不会自己终止,而指明异常的可以抛出没有指明的异常,这使得这种方式非常没用。
void Noexception() throw()
{
throw "你好";
}
void hasexception() throw(SqlException, HttpServerException)
{
throw "helloworld";
}
int main()
{
try
{
hasexception();
}
catch (const char* str)
{
cout << str << endl;
}
}
c++11中提供了一种新的异常方案:
明确不抛出异常的话,可以加上noexcept关键字,如果可能抛出异常,就不需要加此关键字。
void Noexception() noexcept
{
throw "你好";
}
这样的写法就会直接报错。最好不要在构造函数和析构函数抛出异常,因为这样有可能会导致构造/析构不完全,从而引发一系列的问题。像这样的场景还包括文件操作、上锁解锁等。
异常的优缺点
其实我们能够感受到异常带来的诸多便利,简单介绍一些:
1、使得错误信息更加清晰和丰富,一目了然。
2、相比于错误码需要层层返回错误码,异常会直接跳到最外面捕获的位置。
3、有些特殊场景只能抛出异常,比如有的函数没有返回值,容器越界等(数组越界是未定义的行为,不会抛出异常) 没有返回值的场景。
但有利必有弊,异常也有一些缺点:
1、执行流乱跳,这让我们在断点调试时非常难受。
2、库的异常体系不好,导致大家经常各用各的,很难统一。
3、需要我们规范使用,但这种规范又不强制,导致形同虚设。
4、c++没有垃圾回收机制,很容易出现内存泄漏和死锁等安全问题。
但总的来说,异常的利大于弊,是未来语言处理错误的趋势,我们仍然要学好异常。文章来源:https://www.toymoban.com/news/detail-522962.html
结语
今天的内容就到这里了,我们下次再见~。文章来源地址https://www.toymoban.com/news/detail-522962.html
到了这里,关于【c++修行之路】异常的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!