美文网首页
学习 C++ 的异常处理

学习 C++ 的异常处理

作者: 蟹蟹宁 | 来源:发表于2021-06-30 17:21 被阅读0次

之前很少使用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的指令。

相关文章

网友评论

      本文标题:学习 C++ 的异常处理

      本文链接:https://www.haomeiwen.com/subject/rcurultx.html