文章目录
1. C语言传统处理
- assert()函数直接终止程序。弊端:用户难以接受
- 返回错误码。弊端:需要程序员自己去查找对应的错误
2. C++异常概念
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
- try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
3. 异常的使用
3.1 异常抛出并没有被捕获
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
division(x, y);
}
int main()
{
test();
return 0;
}
//输入:1 0
//程序直接终止
3.2 正确使用
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (const char* message)
{
cout << message << endl;
}
}
int main()
{
test();
return 0;
}
3.3 捕获异常采用类型匹配就近原则
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (const char* message)
{
cout << "test()" << endl;
cout << message << endl;
}
}
int main()
{
try
{
test();
}
catch (const char* message)
{
cout << "main()" << endl;
cout << message << endl;
}
return 0;
}
//输入:1 0
//输出:
//test()
//division by zero condition!
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (char message)
{
cout << "test()" << endl;
cout << message << endl;
}
}
int main()
{
try
{
test();
}
catch (const char* message)
{
cout << "main()" << endl;
cout << message << endl;
}
return 0;
}
//输入:1 0
//输出:
//main()
//division by zero condition!
3.4 catch(…)可以捕获任意类型异常
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (...)
{
cout << "test()" << endl;
}
}
int main()
{
try
{
test();
}
catch (const char* message)
{
cout << "main()" << endl;
cout << message << endl;
}
return 0;
}
//输入:1 0
//输出:
//test()
3.5 抛出派生类对象使用基类捕获
#include <iostream>
using namespace std;
class Exception
{
protected:
int _code; //错误码
std::string _message; //错误信息
public:
Exception(int errcode, const std::string& errmessage)
:_code(errcode)
,_message(errmessage)
{}
~Exception() {}
int getErrcode() const
{
return _code;
}
const string& getMessage() const
{
return _message;
}
};
class DivisionException : public Exception
{
private:
typedef Exception Base;
public:
DivisionException(int errcode, const std::string& errmessage)
:Base(errcode, errmessage)
{}
~DivisionException() {}
};
double division(int x, int y)
{
if (y == 0)
throw DivisionException(1, string("division by zero condition!"));
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (const Exception& exception)
{
cout << "errcode:" << exception.getErrcode() << endl << "errmessage:" << exception.getMessage() << endl;
}
}
int main()
{
test();
return 0;
}
3.6 异常重新抛出
#include <iostream>
using namespace std;
double division(int x, int y)
{
if (y == 0)
throw "division by zero condition!";
return (double)x / (double)y;
}
void test()
{
int x, y;
cin >> x >> y;
try
{
division(x, y);
}
catch (const char* message)
{
cout << "test()" << endl;
cout << message << endl;
throw;
}
catch (...)
{
cout << "unknown exception!" << endl;
}
}
int main()
{
try
{
test();
}
catch (...)
{
cout << "main()" << endl;
}
return 0;
}
//输入:1 0
//输出
//test()
//division by zero condition!
//main()
3.7 匹配规则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。
- catch(…)可以捕获任意类型的异常
- 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获
- 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。如果到达main函数的栈,依旧没有匹配的,则终止程序
3.8 异常规范
- 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些
- 函数的后面接throw(),表示函数不抛异常
- 若无异常接口声明,则此函数可以抛掷任何类型的异常
// 这里表示这个函数会抛出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;
3.9 异常安全
- 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
- 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏
- new/malloc/fopen/lock等都可能会导致资源泄露问题
4. 自定义异常体系
![](https://typora130.oss-cn-nanjing.aliyuncs.com/QQ截图20230704205147.png)
#include <iostream>
#include <string>
#include <thread>
using namespace std;
class Exception
{
protected:
int _code; //错误码
std::string _message; //错误信息
public:
Exception(int errcode, const std::string& errmessage)
:_code(errcode)
,_message(errmessage)
{}
~Exception() {}
public:
int getErrcode() const
{
return _code;
}
const string& getMessage() const
{
return _message;
}
virtual std::string what() const
{
return std::to_string(_code) + _message;
}
};
class SQLException : public Exception
{
private:
typedef Exception Base;
private:
std::string _SQLstatement; //SQL语句错误
public:
SQLException(int errcode, const std::string& errmessage, const string& SQLstatement)
:Base(errcode, errmessage)
,_SQLstatement(SQLstatement)
{}
~SQLException() {}
public:
virtual string what() const
{
string message = "SQLException:" + std::to_string(_code) + " " + _message + "->" + _SQLstatement;
return message;
}
};
class CacheException : public Exception
{
private:
typedef Exception Base;
public:
CacheException(int errcode, const std::string& errmessage)
:Base(errcode, errmessage)
{}
~CacheException() {}
public:
virtual string what() const
{
string message = "CacheException:" + std::to_string(_code) + " " + _message;
return message;
}
};
class HttpServerException : public Exception
{
private:
typedef Exception Base;
private:
std::string _type; //错误类型
public:
HttpServerException(int errcode, const std::string& errmessage, const std::string& errtype)
:Base(errcode, errmessage)
,_type(errtype)
{}
~HttpServerException() {}
public:
virtual string what() const
{
string message = "HttpServerException:" + std::to_string(_code) + " " + _message + "->" + _type;
return message;
}
};
void SQL()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SQLException(7, "permission denied", "select * from name = '张三'");
}
cout << "success" << endl;
}
void cache()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException(5, "permissiong denied");
}
else if (rand() % 6 == 0)
{
throw CacheException(6, "data do not exsit");
}
SQL();
}
void httpServer()
{
srand(time(nullptr));
if (rand() % 3 == 0)
throw HttpServerException(3, "Request insufficient resources", "get");
if (rand() % 4 == 0)
throw HttpServerException(4, "permission denied", "post");
cache();
}
void test()
{
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
try
{
httpServer();
}
catch (const Exception& exception)
{
cout << exception.what() << endl;
}
catch (...)
{
cout << "unknown exception!" << endl;
}
}
}
int main()
{
test();
return 0;
}
5. 异常优缺点
5.1 优点
- 相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
- 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常
- 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误
5.2 缺点
- 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳
- 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
- C++没有垃圾回收机制,资源需要自己管理
- C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱
- 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言