最近在CodeReview.SE上,我遇到了an answer,其中谈到了一种称为“异常呕吐”的技术。显然,这个技巧用于利用必须以线程安全的方式实现异常,而不管编译器是否支持thread_local
变量。
我粘贴以下答案的一部分:
现有的技术并不相同,称为"异常呕吐"。观察:
void f(void(*p)()) { p(); } template<typename F> void real_f(F func) { try { throw func; } catch(...) { f([] { try { throw; } catch(F func) { func(); } }); } }
这滥用了这样的事实:编译器必须为复杂对象提供线程局部堆栈以用作异常存储,而不管它们是否支持其他线程本地功能,因此可以获得广泛的编译器支持。最明显的缺点是a)它太可怕了,b)它只限于堆栈语义。
我的问题是,这个技巧实际上是如何运作的,它是否“安全”?
答案 0 :(得分:5)
此技术依赖于以下事实:必须以线程安全的方式实现异常,以便在多线程应用程序中使用异常。在线程成为C ++标准的一部分之前,即使是前C ++ - 11编译器也支持线程安全异常。
每个线程throw
/ catch
独立于其他线程,通过使用特定于线程的存储来存储异常。没有参数的throw
重新抛出存储在该特定于线程的存储中的当前异常。此异常存储用于存储具有捕获的参数的函数(有状态的lambda或任何其他可调用的函数)。
此技术的缺点是抛出异常通常涉及内存分配,因此会增加new
/ delete
调用的开销。
实现此目的的另一种方法是使用不可移植但广泛支持的__thread
存储说明符。这样可以避免动态内存分配的开销:
void f(void(*p)()) { // The C-style function.
p();
}
__thread void(*function)();
template<class Function>
void adapter() {
(*reinterpret_cast<Function*>(function))();
}
template<typename F>
void invoke_f(F const& func) {
function = reinterpret_cast<void(*)()>(&func);
f(adapter<F const>);
}
int main(int ac, char**) {
invoke_f([ac]{ std::cout << ac << '\n'; });
}