我有一个类,它将用户给出的工作分配给多个线程。像(简化)的东西:
class MT {
public:
MT();
void work1(/*args*/);
void work2(/*args*/);
void work3(/*args*/);
//waits until all threads complete, returns whether the work has been completed
//successfully, throws any exceptions that have been raised in the threads.
bool is_ok();
~MT();
private:
// thread pool
}
用户按如下方式使用该类:
void foo()
{
MT mt;
mt.work1(/*userdata*/);
mt.work2(/*userdata*/);
mt.work1(/*userdata*/);
status = mt.is_ok();
if (status) {
mt.work3(/*userdata*/);
//...
}
//...
}
该类永远不会成为某个对象的一部分,始终存储在堆栈中。
我想以某种方式发出在其他线程中工作时引发的任何异常。不幸的是,我知道线程是否在加入后才成功完成。因此,我必须在以下两种选择中做出选择:
加入MT
的析构函数中的线程,并抛出在工作时出现的异常(如果有的话)。如果抛出了多个异常,请从最早的任务中选择一个异常。如果调用析构函数进行堆栈展开(我们可以使用std::uncaught_exception
进行检查,请吞下任何异常以阻止terminate()
。
指示用户始终在析构函数前调用is_ok
。
我认为第一个选项更干净,因为用户不需要调用任何东西。但是,通常非常强烈地不鼓励从析构函数中抛出。提出的论点是:
terminate()
。不知何故,我倾向于认为上述论点不适用于此:
foo
的任何异常都表示工作失败。投掷或不投掷析构函数并不是行为的重大改变。答案 0 :(得分:7)
不,不要从析构函数中抛出。致电is_ok
,抓住并忽略。与流的close()
相同。
用户可以调用is_ok
,如果他们想确定工作已经完成,则会在失败时抛出异常。
在实践中,这很少是不方便的。如果用户编写具有多个返回点的函数,但是(不幸的是)这是他们必须处理的问题,因为C ++不提供为您执行此操作的方法。如果你认为这是反社交的,那么,看看如果你在C ++ 11中销毁std::thread
而没有先加入或删除它会发生什么。你打败了: - )
因此,用户在所有非错误退出路径上调用is_ok
。当已经存在异常时,用户不会打扰它,因为无论如何它们都无法处理另一个异常。同样,这与流的close()
相同:如果您希望通过编写缓冲流来查看错误,那么您只需要显式关闭或刷新。
即使上述问题得到解决,投掷或不投掷取决于 堆栈是否被解开是行为的重大变化 应该气馁。
至少在C ++ 03中,也不可能正确地做到这一点。我不知道C ++ 11在这方面是否有任何改变,但是std::uncaught_exception
不会告诉你需要知道什么,as Herb Sutter explains。
从您的链接到cppreference.com:
std :: uncaught_exception()== true时抛出的任何异常都会调用std :: terminate。
我很确定这是假的。如果异常转义为作为堆栈展开的一部分调用的析构函数,则调用terminate
,但不会因为析构函数在展开期间抛出并捕获异常而调用它。
如果调用者认为他们可以对析构函数中抛出的异常做一些有用的事情,那么他们可以编写一个帮助类,在其析构函数中调用is_ok
,并将其放在你的宾语。如果你认为你的类可以使用相同的异常做一些有用的事情,那么除了在析构函数中忽略它之外你可以做一些事情,但你仍然不应该让它离开析构函数。