美文网首页
学习 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