链接异常的实现是如何工作的?
我之前问过一个关于如何在 C++ 中链接异常的问题,其中之一这些答案为如何做到这一点提供了一个绝妙的解决方案。问题是我看不懂代码,试图在评论中进行这种讨论实在是太麻烦了。所以我认为最好完全开始一个新问题。
代码包含在下面,我已经清楚地标记了我不明白的每个部分。代码下面包含了我不明白的内容的描述。该代码由 Potatoswatter 编写。
代码
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;
// ----------------------------------------------------------------
// 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();
}
运行代码给出以下输出:
bah
humbug!
meh!
throw;
inside try
-block:我不明白的内容
- :我从来没有以前见过这个。我认为 throw; 唯一有效的地方是在 catch 块内,以重新抛出捕获的内容。那么这有什么作用呢?一些调试显然表明抛出的异常是之前抛出的异常,但那是在完全不同的
try
块内。事实上,它甚至位于struct
声明之外! 交换字段:为什么我们需要交换异常字段?仅仅复制指针还不够吗?这是为了防止字段指向的结构被过早地从堆中删除吗?
检查
link
和link
的链接:我可以理解检查link 不是 NULL
(即使删除 NULL 指针也没有效果),但是为什么需要检查link
的链接呢?抛出虚拟异常:为什么需要这个虚拟异常?它被抛出,但随后掉落。为什么我们需要这个作为链条的末端?
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() {}
};
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
聪明的代码——对potatoswatter 的这一代码表示敬意。我认为我必须找到解决最后一项的方法。
throw;
重新抛出活动异常。仅当catch
块位于堆栈上时它才有效。我不记得我是在哪里看到这个花絮的,但它可能是在其他一些问题的背景下出现的。裸抛出让我们可以通过在chained_exception
构造函数中捕获当前异常来访问它。换句话说,构造函数中的prev
是对我们当前正在处理的异常的引用。你是对的。这可以防止重复删除。
哨兵异常,即在
main
中抛出的异常,永远不应该被删除。此异常的一个标识属性是它的link
成员为NULL
。这是我不喜欢的部分,但想不出一个简单的方法。只有当
catch
块处于活动状态时,才能调用唯一可见的chained_exception
构造函数。 IIRC,没有活动catch
块的裸露抛出是禁忌。因此,解决方法是放入main
并将所有代码放入catch
块中。现在,如果您在多线程代码中尝试此方法,请确保您很好地理解 (4)。您必须在线程入口点中复制此内容。
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.