是否应该在C++中链接异常?

gab*_*lin 10 c++ exception chaining nested-exceptions

我刚刚完成了一个C++程序,我已经实现了自己的异常(虽然派生自std :: exception).当一个异常导致连锁反应,向上传播错误并引起其他异常时,我应用的做法是在模块的每个适当步骤(读取类)中连接错误消息.即旧的异常本身被删除并创建一个新的异常,但是有一个更长的错误消息.

这可能适用于我的小程序,但我最终对我的方法不太满意.例如,除最后一个异常外,不保留行号(虽然目前不应用)和文件名; 实际上,在第一个例外中,这些信息最受关注.

我认为通过将异常链接在一起可以更好地处理这个问题; 即在新异常的构造函数中提供旧异常.但是如何实施呢?当它们超出方法的范围时,异常是否会死亡,从而阻止使用异常指针?如果异常可以是任何派生类,如何复制和存储异常?

这最终促使我考虑在C++中链接异常是否是一个好主意.也许应该只创建一个异常然后添加额外的数据(就像我一直在做的那样,但可能会以更好的方式)?

你对此有何回应?是否应将由另一个引起的异常链接在一起以保留某种"异常追踪" - 以及如何实施? - 或者是否应该使用单个例外并附加其他数据 - 应该如何做?

GPM*_*ler 5

自从提出这个问题以来,C++11 的标准已经发生了显着的变化。我在关于异常的讨论中不断错过这一点,但以下方法,嵌套异常,可以解决问题:

使用std::nested_exceptionstd::throw_with_nested

StackOverflow在此处此处对此进行了描述,您可以通过简单地编写适当的异常处理程序来重新抛出嵌套异常,从而在无需调试器或繁琐的日志记录的情况下获得代码中异常的回溯

由于您可以使用任何派生的异常类来执行此操作,因此您可以向此类回溯添加大量信息!您也可以在 GitHub 上查看我的MWE,其中的回溯如下所示:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
Run Code Online (Sandbox Code Playgroud)


Pot*_*ter 2

catch如果您希望数据比接收它的块寿命更长(除了由 重新抛出),则有必要将数据从异常对象复制到链中throw;。(例如,这包括如果该catch块通过throw obj;。)

\n\n

这可以通过将要保存的数据放在堆上并实现swap(move例如,

\n\n

当然,在使用带有异常\xe2\x80\xa6的堆时需要小心,但话又说回来,在大多数现代操作系统中,内存过量使用完全阻止了new抛出异常,无论好坏。良好的内存裕度和在完全崩溃时从链中删除异常应该可以保证它的安全。

\n\n
struct exception_data { // abstract base class; may contain anything\n    virtual ~exception_data() {}\n};\n\nstruct chained_exception : std::exception {\n    chained_exception( std::string const &s, exception_data *d = NULL )\n        : data(d), descr(s) {\n        try {\n            link = new chained_exception;\n            throw;\n        } catch ( chained_exception &prev ) {\n            swap( *link, prev );\n        } // catch std::bad_alloc somehow...\n    }\n\n    friend void swap( chained_exception &lhs, chained_exception &rhs ) {\n        std::swap( lhs.link, rhs.link );\n        std::swap( lhs.data, rhs.data );\n        swap( lhs.descr, rhs.descr );\n    }\n\n    virtual char const *what() const throw() { return descr.c_str(); }\n\n    virtual ~chained_exception() throw() {\n        if ( link && link->link ) delete link; // do not delete terminator\n        delete data;\n    }\n\n    chained_exception *link; // always on heap\n    exception_data *data; // always on heap\n    std::string descr; // keeps data on heap\n\nprivate:\n    chained_exception() : link(), data() {}\n    friend int main();\n};\n\nvoid f() {\n    try {\n        throw chained_exception( "humbug!" );\n    } catch ( std::exception & ) {\n        try {\n            throw chained_exception( "bah" );\n        } catch ( chained_exception &e ) {\n            chained_exception *ep = &e;\n            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {\n                std::cerr << ep->what() << std::endl;\n            }\n        }\n    }\n\n    try {\n        throw chained_exception( "meh!" );\n    } catch ( chained_exception &e ) {\n        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {\n            std::cerr << ep->what() << std::endl;\n        }\n    }\n}\n\nint main() try {\n    throw chained_exception(); // create dummy end-of-chain\n} catch( chained_exception & ) {\n    // body of main goes here\n    f();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出(适当地脾气暴躁):

\n\n
bah\nhumbug!\nmeh!\n
Run Code Online (Sandbox Code Playgroud)\n