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

时间:2010-08-22 22:31:21

标签: c++ exception chaining nested-exceptions

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

这对我的小程序可能有用,但最后我的方法并不是很满意。例如,除最后一个异常外,不保留行号(虽然目前不应用)和文件名;而且在第一个例外情况下,这些信息最为重要。

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

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

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

4 个答案:

答案 0 :(得分:4)

由于已经提出这个问题,因此使用C ++ 11对标准进行了可观的更改。 我在关于异常的讨论中不断遗漏这一点,但是下面的方法,嵌套异常,可以解决这个问题:

使用std::nested_exceptionstd::throw_with_nested

StackOverflow herehere中描述了如何在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需简单编写一个适当的异常处理程序,它将重新抛出嵌套的异常。

由于您可以对任何派生的异常类执行此操作,因此可以向此类回溯添加大量信息! 您还可以查看我的MWE on GitHub,其中回溯看起来像这样:

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"

答案 1 :(得分:1)

除了catch的重新抛出之外,如果您希望它比接收它的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!

答案 2 :(得分:1)

您可能希望看一下:http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

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

答案 3 :(得分:0)

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

我敢打赌,由于任何堆栈信息是否完全可用是实现定义的,因此无论是否在裸throw;语句之后以任何方式保留它,实现都会有更大的变化。