Should exceptions be chained in C++? [duplicate]

删除回忆录丶 提交于 2019-11-30 08:56:42

It is necessary to copy the data out of an exception object, into a chain, if you want it to outlive the catch block that receives it, aside from rethrow by throw;. (Which includes, for example, if that catch block exits through a throw obj;.)

This can be done by putting data to be saved on the heap, and implementing swap (move in C++0x) on your private data inside the exception, for example.

Of course, you need to be careful when using the heap with exceptions… but then again, in most modern OSes, memory overcommitment completely prevents new from ever throwing, for better or for worse. A good memory margin and dropping exceptions from the chain upon complete meltdown should keep it safe.

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();
}

output (appropriately grumpy):

bah
humbug!
meh!

Since this question has been asked noteable changes have been made to the standard with C++11. I am continually missing this in discussions about exceptions, but the following approach, nesting exceptions, does the trick:

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

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"

You may want to look at this: http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

It's somewhat different approach to what MS did in C#, but it seems to match your requirements.

Another idea is to add the relevant data to your exception object then use a bare throw; statement to re-throw it. I think the stack information is retained in this case, and so you'll still know the original source of the exception, but testing would be a good idea.

I bet since whether or not any stack information is available at all is implementation defined, that implementations will vary even more widely in whether or not it's preserved in any way after a bare throw; statement.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!