什么是“例外呕吐”?

时间:2017-07-11 09:19:47

标签: c++ exception-handling thread-safety

最近在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)它只限于堆栈语义。

我的问题是,这个技巧实际上是如何运作的,它是否“安全”?

1 个答案:

答案 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'; });
}