How does this implementation of chaining exceptions work?
I previously asked a question about how to chaining exceptions in C++, and one of the answers provided a nifty solution to how it can be done. The problem is that I don't understand the code, and trying to have this kind of discussion in the comments is just too much of a bother. So I figured it's better to start a new question entirely.
The code is included below and I've clearly marked each section which I don't get. A description of what I don't understand is included below the code. The code was written by Potatoswatter.
Code
struct exception_data { // abstract base class; may contain anything
virtual ~exception_data() {}开发者_如何学C
};
struct chained_exception : std::exception {
chained_exception( std::string const &s, exception_data *d = NULL )
: data(d), descr(s) {
try {
link = new chained_exception;
// ----------------------------------------------------------------
// How does this work (section 1)?
throw;
// ----------------------------------------------------------------
} catch ( chained_exception &prev ) {
// ----------------------------------------------------------------
// How does this work (section 2)?
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() {
// --------------------------------------------------------------------
// How does this work (section 3)?
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 ) {
// Print ep->what() to std::cerr
}
}
}
try {
throw chained_exception( "meh!" );
} catch ( chained_exception &e ) {
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
// Print ep->what() to std::cerr
}
}
}
int main() try {
// ------------------------------------------------------------------------
// How does this work (section 4)?
throw chained_exception(); // create dummy end-of-chain
// ------------------------------------------------------------------------
} catch( chained_exception & ) {
// body of main goes here
f();
}
Running the code gives the following output:
bah
humbug!
meh!
What I don't understand
throw;
insidetry
-block: I've never seen this before. The only place I've thoughtthrow;
to be valid was inside acatch
-block to rethrow what was caught. So what does this do? Some debugging apparently shows that the thrown exception is what was thrown previously, but that was inside a completely differenttry
-block. In fact, it was even outside thestruct
declaration!Swap fields: Why do we need to swap the exception fields? Wouldn't just copying of the pointers be enough? Is this to prevent the structures to which the fields point at from being deleted from the heap prematurely?
Check
link
andlink
's link: I can understand checking thatlink
is notNULL
(even though deleting aNULL
pointer has no effect), but why the need to check thelink
's link?Throw dummy exception: Why is this dummy needed? It's thrown but then dropped. Why do we need this as an end to the chain?
Clever code - kudos to potatoswatter on this one. I think that I would have to find some way around the last item though.
throw;
rethrows the active exception. It is only valid if acatch
block is on the stack. I can't recall where I came across that tidbit at but it was probably on SO in the context of some other question. The bare throw gives us access to the current exception by catching it in thechained_exception
constructor. In other words,prev
in the constructor is a reference to the exception that we are currently processing.You are correct here. This prevents double deletion.
The sentinel exception, the one thrown in
main
, should never be deleted. The one identifying attribute of this exception is that it'slink
member isNULL
.This is the part that I don't like but cannot think of an easy way around. The only visible
chained_exception
constructor can only be called when acatch
block is active. IIRC, a bare throw without an activecatch
block is a no-no. So, the workaround is to throw inmain
and put all of your code in thecatch
block.
Now, if you try this method in multi-threaded code, make sure that you understand (4) very well. You will have to replicate this in your thread entry point.
精彩评论