- C语言处理错误的方式:
(1)终止程序。如assert、exit,缺陷:用户难以接受。例如:发生了内存错误,除数为0错误时就会终止程序。
(2)返回错误码。缺陷:需要程序员自己去查找对应的错误。例如:系统的很多库接口函数都是通过把错误码传到errno中,表示错误。
(3)C语言标准库中有setjmp和longjmp组合。
一、C++异常概念
- 异常是一种处理错误的方式。当一个函数发现自己无法处理的错误时就可以抛出异常。让函数的直接或间接的调用者处理这个错误。
1.关键字
- throw:抛出异常的关键字。
- catch:捕获异常的关键字。
- try:try块中的代码标识被激活的特定异常,后面通常跟着一个或多个catch。
2.异常终止程序执行
#include <iostream>
#include <windows.h>
using namespace std;
double Division(int a, int b)
{
if (b == 0)
{
throw "除0错误";
}
else
{
return (double)a / (double)b;
}
}
void func()
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
int main()
{
try
{
func();
}
catch (const char* errostr)
{
cout << errostr << endl;
}
catch (...)
{
cout << "Unkown Error" << endl;
}
return 0;
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200528220922986.png)
3.重新抛异常
void func()
{
int* array = new int[10];
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
delete[] array;
}
- 上述代码存在异常安全问题 ==》内存泄漏。因为抛出异常之后,就不会执行后面的delete[] array这句代码。
- 解决方法:1.中间捕获异常处理完异常之后再抛异常。(再抛出异常的理由是一般异常处理都是在最外层代码处理的)
- 如下所示:
#include <iostream>
#include <windows.h>
using namespace std;
double Division(int a, int b)
{
if (b == 0)
{
throw "除0错误";
}
else
{
return (double)a / (double)b;
}
}
void func()
{
int* array = new int[10];
try
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
catch (...)
{
delete[] array;
throw;
}
delete[] array;
}
int main()
{
try
{
func();
}
catch (const char* errostr)
{
cout << errostr << endl;
}
catch (...)
{
cout << "Unkown Error" << endl;
}
return 0;
}
二、异常安全
- 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化。
- 析构函数完成资源的清理,最好不要在析构函数中抛出异常,否则可能导致资源泄露(内存泄露、句柄未关闭等情况。)
- new和delete之间抛异常注意可能会导致内存泄漏。
- lock和unlock之间抛异常可能会导致死锁。
三、异常规范
- 异常规格说明是为了让函数使用者知道该函数可能抛出的异常有哪些。可以在函数后面接throw(类型),列出该函数可能抛掷的所有异常的类型。
- 函数后面接throw(),表示该函数不抛异常。
- 若没有异常接口声明,表示该函数可以抛出任何类型的异常。
void func() throw(char*, int, double);
void* operator new(size_t size) throw(bad_alloc);
void func() throw();
四、自定义异常体系
- 因为异常可以抛出任意类型的异常,也就是说捕获的地方也得各种类型都捕获,那么这代码就很难控制,故在工程中不会让抛出任意类型异常,而是进行一定程度的异常的规范处理。实际中都会定义一套继承的规范体系,这样大家抛出的异常都是派生类对象,捕获一个基类就可以了。
#include <iostream>
#include <string>
#include <windows.h>
using namespace std;
class Exception
{
public:
Exception(const char* err, int id)
:_err(err)
,_id(id)
{}
virtual void Print() const = 0;
protected:
string _err;
int _id;
};
class SqlException :public Exception
{
public:
SqlException(const char* err, int id)
:Exception(err, id)
{}
void Print()const
{
cout << "SqlException" << _err << "+" << _id << endl;
}
};
class HttpException :public Exception
{
public:
HttpException(const char* err, int id)
:Exception(err, id)
{}
void Print()const
{
cout << "HttpException" << _err << "+" << _id << endl;
}
};
void Start()
{
if (rand() % 3 == 0)
throw SqlException("Sql语句错误", 1);
if (rand() % 5 == 0)
throw HttpException("HttpException语句错误", 2);
::Sleep(1000);
}
int main()
{
while (1)
{
try
{
Start();
}
catch (const Exception& e)
{
e.Print();
}
catch (...)
{
cout << "Unknown Error" << endl;
}
}
return 0;
}
五、C++标准库的异常体系
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
using namespace std;
void func()
{
if (rand() % 3 == 0)
char* p = new char[0x7fffffff];
if (rand() % 5 == 0)
{
vector<int> v = { 0, 1, 2 };
v.at(3);
}
::Sleep(1000);
}
int main()
{
while (1)
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unknown Error" << endl;
}
}
return 0;
}
六、异常的优缺点
1.异常的优点
- 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆调用的信息。这样就可以帮助更好的定位程序的bug。
- 返回错误码的方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,我们得到层层返回的错误,到最外层才能拿到错误,而程序不会在异常那停止运行。
- 很多的第三方库都包含异常,比如boost、gtest等常用的库,那么我们使用它也需要使用到异常。
- 很多的测试框架都使用异常,这样能更好的进行单元测试等白盒测试。
- 部分函数使用异常更好处理,比如构造函数没有返回值。不方便使用错误码方式处理。
2.异常的缺点
- 异常会导致程序的执行流乱跳,且混乱,运行时出错抛出异常就会乱跳,会影响我们跟踪调试和分析程序。
- 异常会有一些性能的开销。
- C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。
- C++标准库的异常体系定义得不好。
- 异常尽量规范使用:1.抛出异常类型都继承自一个基类。2.函数是否抛异常,抛什么异常,都使用func() throw();的方式规范化。