传统的错误处理机制:
异常是处理错误的一种方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或间接的调用者处理这个错误
throw
关键字完成的如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示
try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
异常的抛出和匹配原则
if (b == 0)
{
string str("Division by zero condition!");
throw str;
}
在函数调用链中异常栈展开匹配原则
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()
{
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (const char* err)
{
cout << err << endl;
}
cout << "xxxxxxxxxxxxxx" << endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...) {
cout << "unkown exception" << endl;
}
return 0;
}
执行顺序分析
main
函数执行:
try
块,调用 Func()
函数。Func
函数执行:
Func
函数后,执行 try
块。try
块中,cin >> len >> time;
读取输入,len
被赋值为 3,time
被赋值为 0。Division(len, time)
,即 Division(3, 0)
。Division
函数执行:
Division
函数中,b == 0
,因此会抛出异常 throw "Division by zero condition!";
。Func
中的 catch (const char* err)
块。Func
中的异常处理:
catch (const char* err)
块中,捕获到了异常 "Division by zero condition!"
。Division by zero condition!
。cout << "xxxxxxxxxxxxxx" << endl;
会执行,输出:xxxxxxxxxxxxxx
。main
函数的异常处理:
Func
函数中已经处理了异常,所以 main
函数中的 catch
块不会再执行,main
函数正常结束。关键点解释
Division
函数中被抛出,Func
函数内的 catch
块捕获并处理了这个异常。Func
已经处理了异常,main
函数的 catch
块不会再被执行。Func
在处理完异常后继续执行,打印了 "xxxxxxxxxxxxxx"
。有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array << endl;
delete[] array;
throw;
}
// ...
cout << "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
Func
函数
void Func()
{
int* array = new int[10]; // 在堆上分配一个数组
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl; // 调用可能抛出异常的函数
}
catch (...)
{
cout << "delete []" << array << endl; // 先释放资源
delete[] array;
throw; // 重新抛出异常,让外层处理
}
cout << "delete []" << array << endl;
delete[] array;
}
int* array = new int[10];
创建一个动态数组,在 Func
结束前需要释放。Division(len, time)
: time == 0
,则 Division
抛出异常 "Division by zero condition!"
。catch (...)
捕获所有异常: catch
块中,delete[] array;
释放动态数组,防止内存泄漏。throw;
重新抛出异常,让 main
处理。main
函数
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
try
块调用 Func()
,如果 Func
抛出异常,catch (const char* errmsg)
负责捕获它并打印错误信息。异常被 Func
捕获
catch (...)
捕获所有异常。
cout << "delete []" << array << endl;
先打印内存释放信息:
delete []0x600003fae040
delete[] array;
释放数组。
throw;
重新抛出相同的异常。
异常传递到 main
catch (const char* errmsg)
捕获 throw
重新抛出的异常,并打印错误信息:
Division by zero condition!
main
结束,程序退出。
1. throw;
语句
catch (...)
捕获了所有异常,但不想处理它,而是想让上层 try
处理。throw;
而不是 throw someException;
,否则会切断原始异常类型的信息。throw;
只是重新抛出原始异常,所以 main
中的 catch (const char* errmsg)
仍然可以正确捕获 Division by zero condition!
。2. 资源释放
delete[] array;
确保异常发生时不会造成内存泄漏。throw;
在 delete[]
之前,程序会泄露 array
的内存。delete[]
仍然会在 Func
结束时执行,所以无论如何,内存都会被正确释放。// 这里表示这个函数会抛出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;
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了
// 服务器开发中通常使用的异常继承体系
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 SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
:Exception(errmsg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errmsg;
return str;
}
};
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 SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
else
cout << "调用Sql成功" << endl;
//throw "xxxxxx";
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
else
cout << "Cache获取成功" << endl;
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;
}
这段代码展示了 服务器开发中常见的异常继承体系,它通过 异常类层次结构 以及 多态捕获机制,在
main
函数中统一处理不同类型的异常。
1. 代码的核心目标
Exception
,并让各种子系统(SQL、缓存、HTTP)抛出不同类型的异常。Exception
统一捕获所有异常,而不需要针对每种异常类型分别处理。HttpServer()
调用链中,异常可能在 SQL、缓存或 HTTP 层抛出,并在 main
中统一捕获。class Exception
{
public:
Exception(const string& errmsg, int id)
:_errmsg(errmsg)
, _id(id)
{}
virtual string what() const
{
return _errmsg;
}
protected:
string _errmsg;
int _id;
};
基类 Exception
_errmsg
(错误信息)_id
(错误编号)what()
: virtual
关键字,支持多态。1. SQL 相关异常
class SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
:Exception(errmsg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
特性:
Exception
,用于表示 SQL 相关异常(如权限不足、SQL 语法错误等)。_sql
字段 用于存储出错的 SQL 语句。what()
重新定义: SqlException:权限不足->select * from name = '张三'
这样的格式信息。2. 缓存系统相关异常
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errmsg;
return str;
}
};
特性:
Exception
,表示 缓存相关异常(如数据不存在、权限不足等)。CacheException:权限不足
这样的格式。3. HTTP 服务器相关异常
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;
};
特性:
Exception
,表示 HTTP 服务器异常(如请求资源不存在、权限不足等)。_type
,存储 HTTP 请求类型(如 "GET"
、"POST"
)。what()
重新定义: "HttpServerException:GET:请求资源不存在"
这样的格式。1. SQL 相关操作
void SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
else
cout << "调用Sql成功" << endl;
}
SqlException("权限不足", 100, "select * from name = '张三'")
。"调用Sql成功"
。2. 缓存管理
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
else
cout << "Cache获取成功" << endl;
SQLMgr();
}
"权限不足"
。"数据不存在"
。"Cache获取成功"
并继续调用 SQLMgr()
(可能导致 SqlException
)。3. HTTP 服务器请求
void HttpServer()
{
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
"请求资源不存在"
(GET
)。"权限不足"
(POST
)。CacheMgr()
,可能导致 CacheException
或 SqlException
。int main()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1)); // 每次请求间隔 1s
try {
HttpServer(); // 可能抛出 HttpServerException, CacheException, SqlException
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
cout << e.what() << endl; // 多态,调用具体异常的 `what()` 方法
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
执行流程
HttpServer()
可能抛出 HttpServerException
,或者调用 CacheMgr()
,进而可能抛出 CacheException
或 SqlException
。catch (const Exception& e)
捕获(多态机制),然后调用 e.what()
打印错误信息。catch (...)
兜底处理,输出 "Unknown Exception"
。可能的输出示例
Cache获取成功
调用Sql成功
HttpServerException:post:权限不足
SqlException:权限不足->select * from name = '张三'
CacheException:数据不存在
C++ 标准库提供了一套完善的异常体系,以处理各种类型的运行时错误和异常情况
基类 std::exception
std::exception
是所有标准库异常的基类。它定义在 <exception>
头文件中,并提供以下成员函数:
class exception {
public:
exception() noexcept;
exception(const exception&) noexcept;
exception& operator=(const exception&) noexcept;
virtual ~exception();
virtual const char* what() const noexcept;
};
what()
函数返回一个 C 风格字符串,描述异常的具体原因。
以下是 C++ 标准库中常见的异常派生层次结构:
std::exception
|
|-- std::bad_alloc
|
|-- std::bad_cast
|
|-- std::bad_typeid
|
|-- std::bad_function_call
|
|-- std::bad_weak_ptr
|
|-- std::logic_error
| |-- std::domain_error
| |-- std::invalid_argument
| |-- std::length_error
| |-- std::out_of_range
|
|-- std::runtime_error
|-- std::range_error
|-- std::overflow_error
|-- std::underflow_error
1. std::bad_alloc
std::exception
。new
运算符无法分配内存时抛出,表示内存分配失败。try {
int* p = new int[1000000000000];
} catch (const std::bad_alloc& e) {
std::cerr << e.what() << std::endl;
}
2. std::bad_cast
std::exception
。dynamic_cast
转换失败时抛出。class Base { virtual void dummy() {} };
class Derived : public Base { int a; };
try {
Base* pBase = new Base;
Derived* pDerived = dynamic_cast<Derived*>(pBase); // 失败
if (!pDerived) throw std::bad_cast();
} catch (const std::bad_cast& e) {
std::cerr << e.what() << std::endl;
}
3. std::bad_typeid
std::exception
。typeid
操作一个指向空对象的指针时抛出。#include <typeinfo>
try {
Base* pBase = nullptr;
std::cout << typeid(*pBase).name() << std::endl;
} catch (const std::bad_typeid& e) {
std::cerr << e.what() << std::endl;
}
4. std::logic_error
std::exception
。派生类
std::domain_error
:表示在数学领域上出错的异常。std::invalid_argument
:表示无效参数异常。std::length_error
:表示非法长度异常。std::out_of_range
:表示超出范围异常。// std::invalid_argument 用例
void check_argument(int arg) {
if (arg < 0) throw std::invalid_argument("Argument must be non-negative");
}
// std::out_of_range 用例
try {
std::vector<int> v(5);
std::cout << v.at(10) << std::endl; // 越界访问
} catch (const std::out_of_range& e) {
std::cerr << e.what() << std::endl;
}
5. std::runtime_error
std::exception
。派生类
std::range_error
:表示范围错误异常。std::overflow_error
:表示算术溢出异常。std::underflow_error
:表示算术下溢出异常。// std::overflow_error 用例
try {
int result = std::numeric_limits<int>::max();
result += 1; // 算术溢出
} catch (const std::overflow_error& e) {
std::cerr << e.what() << std::endl;
}
// std::underflow_error 用例
try {
double result = std::numeric_limits<double>::min();
result /= 2; // 算术下溢出
} catch (const std::underflow_error& e) {
std::cerr << e.what() << std::endl;
}
C++11 中的其他异常类
C++11 引入了一些新的标准异常类,用于特定的错误情况:
std::bad_function_call
:当调用 std::function
对象指向的函数时,若对象不包含有效的目标函数,则抛出此异常。
try {
std::function<void()> func;
func(); // 调用空的 std::function 对象
} catch (const std::bad_function_call& e) {
std::cerr << e.what() << std::endl;
}
std::bad_weak_ptr
:当使用 std::weak_ptr
在所有弱引用的目标对象被销毁时锁定目标对象时抛出。
try {
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;
sp.reset(); // 销毁对象
std::shared_ptr<int> sp2 = wp.lock(); // 失败
if (!sp2) throw std::bad_weak_ptr();
} catch (const std::bad_weak_ptr& e) {
std::cerr << e.what() << std::endl;
}
C++异常的优点:
1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,
ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因
为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
// 用户名密码错误
if (...)
return 1;
// 权限不足
if (...)
return 2;
}
int ServerStart() {
if (int ret = ConnnectSql() < 0)
return ret;
int fd = socket()
if(fd < 0)
return errno;
}
int main()
{
if (ServerStart() < 0)
...
return 0;
}
C++异常的缺点: