之前很少使用C++,更极少使用Java,所以对异常这个语言特性一般就是知道个大概,从来没有研究过怎么使用,更别提底层的原理了。
最近研究了Faasm、WASM以及Pistache的源码,对C++有了一定的熟悉,但是还是很少管异常,今天在研究Pistache的异步实现:Promise。其中使用了std::exception_ptr
类型来作为,then()
的RejectFunc的返回参数,所以就学习了一下,异常的知识,才恍然知道,原来是这样!
一、 try{ throw } catch{ }
这是C++的提供的关于异常的三个关键字:
int main(){
try {
throw "exception";
}catch (const char *e){
printf("%s\n",e);
}
try {
throw 3.14;
}catch (const double e){
printf("%.2f\n",e);
}
}
原来语法如此的简单,也就是说,我throw一个数据,然后再用catch接收就行了,非常符合他们起得名字:抛出和抓住。而且我们可以throw任意的数据类型,当然也可以是一个对象:
struct Test
{
Test(const char* s, int i, double d)
: s(s)
, i(i)
, d(d) {};
const char* s;
int i;
double d;
void print() const
{
printf("%s %d %.2f\n", s, i, d);
}
};
int main()
{
try
{
throw Test("LLF", 520, 13.14);
}
catch (const Test& e)
{
e.print();
}
}
二、Exception 类
对上面代码的分析,可以看到,发生异常时抛出一个对象而不是一个简单的数据类型,可以传递更多的错误信息,挺好的。但是这样的话,我们可能针对不同的异常情况定义不同的类,既然这样的话,为何不推出一个统一的异常类,来统一一下呢,而这个统一的类就是Exception
。
看一下定义:
/**
* @brief Base class for all library exceptions.
*
* This is the base class for all exceptions thrown by the standard
* library, and by certain language expressions. You are free to derive
* your own %exception classes, or use a different hierarchy, or to
* throw non-class data (e.g., fundamental types).
*/
class exception
{
public:
exception() noexcept { }
virtual ~exception() noexcept;
exception(const exception&) = default;
exception& operator=(const exception&) = default;
exception(exception&&) = default;
exception& operator=(exception&&) = default;
/** Returns a C-style character string describing the general cause
* of the current error. */
virtual const char* what() const noexcept;
};
主要就是定义了一个what的虚函数,返回C_style的字符串,主要作用就是描述发生一场的原因。
在使用的时候,往往需要自定义一个异常类,以Pistache定义的HttpError为例:
struct HttpError : public std::exception
{
HttpError(Code code, std::string reason);
HttpError(int code, std::string reason);
~HttpError() noexcept override = default;
const char* what() const noexcept override { return reason_.c_str(); }
int code() const { return code_; }
std::string reason() const { return reason_; }
private:
int code_;
std::string reason_;
};
throw和catch也很简单:
int main()
{
try
{
throw Pistache::Http::HttpError(400, "Bad Request");
}
catch (const std::exception& e)
{
printf("%s", e.what());
}
catch (Pistache::Http::HttpError& e)
{
printf("Code:%d, %s", e.code(), e.what());
}
}
当然上面的写法是不太合适的,因为 std::exception 和 Pistache::Http::HttpError 都会被匹配,主要是为了描述用法。
标准异常
C++定义了一些标准的异常,用于各种场景,他们都是继承自std::exception的:到这里关于异常的用法已经是七七八八了。
三、std::exception_ptr
根据官方文档的介绍 std::exception_ptr是一个指向 exception object 的共享智能指针。
关键在于理解 “exception object” 是什么,是std::exception类的对象吗?这种理解是不准的,按我的理解,所谓“exception object” 应该是被throw抛出的对象,根据我们上面的学习,塔既可以是int、double等简单的数据类型、也可以是自定义的类对象,当然也可以是std::exception类对象。
有四个操作std::exception_ptr的函数:
前两个用于生成一个std::exception_ptr,最后一个用于将exception_ptr指向的异常对象重新抛出(重新这个词语是相对于current_exception而言的)。
直接看官方的代码:
#include <iostream> // std::cout
#include <exception> // std::exception_ptr, std::current_exception, std::rethrow_exception
#include <stdexcept> // std::logic_error
int main () {
std::exception_ptr p;
try {
throw std::logic_error("some logic_error exception"); // throws
} catch(const std::exception& e) {
p = std::current_exception();
}
try {
std::rethrow_exception (p);
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << '\n';
}
return 0;
}
- 首先定义了一个 std::exception_ptr变量p
- 然后在第一个try中,抛出了一个标准异常(见上)
- 在第一个catch中,调用
current_exception()
,这样就让p指向了捕获的异常对象 - 然后在第二个try中,调用
rethrow_exception
,将异常重新抛出 - 然后在第二个catch中,依然正常的捕获到了这个异常对象
当然,使用普通的变量或对象也是可以的,下面以make_exception_ptr
为例:
int main()
{
auto p = std::make_exception_ptr(2);
try
{
std::rethrow_exception(p);
}
catch (const int& e)
{
printf("%d\n", e);
}
}
至于底层如何实现的,就暂时不研究了,但是起码知道throw是一种类似JMP的指令。
网友评论