13

我刚刚完成了一个 C++ 程序的工作,在该程序中我实现了自己的异常(尽管派生自 std::exception)。当一个异常导致连锁反应、向上传播错误并引发其他异常时,我采用的做法是在模块(读取类)的每个适当步骤中连接错误消息。即旧异常本身被丢弃并创建一个新异常,但错误消息更长。

这可能对我的小程序有用,但我最终对我的方法并不满意。一方面,除了最后一个例外,不保留行号(虽然目前没有应用)和文件名;确实,该信息对第一个例外最感兴趣。

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

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

您对此有何回应?是否应该将由另一个引起的异常链接在一起以保留一种“异常跟踪”——应该如何实现?-- 或者应该使用一个例外并附加额外的数据 -- 应该怎么做?

4

4 回答 4

6

自从提出这个问题以来,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"
于 2017-10-26T08:32:22.370 回答
2

有必要将异常对象中的数据复制到链中,如果您希望它比catch接收它的块更长寿,除了 rethrow by throw;。(其中包括,例如,如果该catch块通过 . 退出throw obj;。)

例如,这可以通过将要保存的数据放在堆上并在异常内的私有数据上实现swapmove在 C++0x 中)来完成。

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

struct exception_data { // abstract base class; may contain anything
    virtual ~exception_data() {}
};

struct chained_exception : std::exception {
    chained_exception( std::string const &s, exception_data *d = NULL )
        : data(d), descr(s) {
        try {
            link = new chained_exception;
            throw;
        } catch ( chained_exception &prev ) {
            swap( *link, prev );
        } // catch std::bad_alloc somehow...
    }

    friend void swap( chained_exception &lhs, chained_exception &rhs ) {
        std::swap( lhs.link, rhs.link );
        std::swap( lhs.data, rhs.data );
        swap( lhs.descr, rhs.descr );
    }

    virtual char const *what() const throw() { return descr.c_str(); }

    virtual ~chained_exception() throw() {
        if ( link && link->link ) delete link; // do not delete terminator
        delete data;
    }

    chained_exception *link; // always on heap
    exception_data *data; // always on heap
    std::string descr; // keeps data on heap

private:
    chained_exception() : link(), data() {}
    friend int main();
};

void f() {
    try {
        throw chained_exception( "humbug!" );
    } catch ( std::exception & ) {
        try {
            throw chained_exception( "bah" );
        } catch ( chained_exception &e ) {
            chained_exception *ep = &e;
            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
                std::cerr << ep->what() << std::endl;
            }
        }
    }

    try {
        throw chained_exception( "meh!" );
    } catch ( chained_exception &e ) {
        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
            std::cerr << ep->what() << std::endl;
        }
    }
}

int main() try {
    throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

输出(适当脾气暴躁):

bah
humbug!
meh!
于 2010-08-22T23:35:20.560 回答
1

你可能想看看这个: http: //www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

这与 MS 在 C# 中所做的方法有些不同,但它似乎符合您的要求。

于 2010-08-23T06:05:30.720 回答
0

另一个想法是将相关数据添加到您的异常对象中,然后使用裸throw;语句重新抛出它。我认为在这种情况下会保留堆栈信息,因此您仍然会知道异常的原始来源,但测试是个好主意。

我敢打赌,因为任何堆栈信息是否可用都是由实现定义的,所以在一个简单的throw;语句之后,它是否以任何方式保留,实现的差异会更大。

于 2010-08-22T22:43:55.073 回答