在这种情况下,从析构函数中抛出是否合适

2 c++ destructor exception

我有一个类将用户给出的工作分配给多个线程.像(简化)的东西:

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
}
Run Code Online (Sandbox Code Playgroud)

用户使用该类如下:

void foo()
{
    MT mt;
    mt.work1(/*userdata*/);
    mt.work2(/*userdata*/);
    mt.work1(/*userdata*/);
    status = mt.is_ok();
    if (status) {
        mt.work3(/*userdata*/);
        //...
    }
    //...
}
Run Code Online (Sandbox Code Playgroud)

该类永远不会成为某个对象的一部分,始终存储在堆栈中.

问题

我想以某种方式发出在其他线程中工作时引发的任何异常.不幸的是,我知道线程是否在加入后才成功完成.因此,我必须在以下两种选择之间做出选择:

  • 加入析构函数中的线程MT并抛出在工作时产生的异常(如果有的话).如果抛出了多个异常,请从最早的任务中选择一个异常.如果调用析构函数进行堆栈展开(我们可以使用std::uncaught_exception,检查这个,吞下任何异常以防止terminate().

  • 指示用户始终is_ok在析构函数之前调用.

我认为第一个选项更干净,因为用户不需要调用任何东西.但是,通常非常强烈地不鼓励从析构函数中抛出.提出的论点是:

  • 从析构函数中抛出是危险的,因为这可能会在堆栈展开期间发生并导致terminate().
  • 即使解决了上述问题,根据堆叠是否被解开而投掷或不投掷是行为的重大改变,应该不鼓励.
  • 例外表示未满足后置条件.析构函数的后置条件是资源清理,这在任何情况下都是可能的.

不知怎的,我倾向于认为上述论点不适用于此:

  • 我正确处理堆栈展开问题
  • 由于使用模式,任何退出的异常都foo意味着工作失败.投掷或不投掷析构函数并不是行为的重大改变.
  • 析构函数的后置条件不仅是资源清理,而且工作已经成功完成.因此,例外应该是适当的.

问题

  • 你认为在这种情况下从析构函数中抛出是否合适?

Ste*_*sop 7

不,不要从析构函数中抛出.打电话is_ok,接听和忽略.与close()流相同.

如果用户is_ok 想要确定工作已经完成,则可以调用,并且在失败时会抛出异常.

在实践中,这很少是不方便的.如果用户编写具有多个返回点的函数,但是(不幸的是)这是他们必须处理的问题,因为C++不提供为您执行此操作的方法.如果你认为这是反社会的,那么,看看如果你std::thread在没有加入或分离它的情况下销毁C++ 11中会发生什么.你打败了:-)

因此,用户调用is_ok所有非错误退出路径.当已经存在异常时,用户不会打扰它,因为无论如何它们都无法处理另一个异常.同样,这与close()流相同:如果您希望通过编写缓冲流来查看错误,那么您只需要显式关闭或刷新.

即使解决了上述问题,根据堆叠是否被解开而投掷或不投掷是行为的重大改变,应该不鼓励.

至少在C++ 03中,它也不可能正确完成.Herb Sutter解释说,我不知道C++ 11在这方面是否有任何改变,但std::uncaught_exception 并没有告诉你需要知道什么.

从您的链接到cppreference.com:

std :: uncaught_exception()== true时抛出的任何异常都会调用std :: terminate.

我很确定这是错误的.terminate如果异常转义为作为堆栈展开的一部分调用的析构函数,则调用它,但不会因为析构函数在展开期间抛出并捕获异常而调用它.

如果来电者认为他们可以做一些有用的事情,在析构函数抛出一个异常,那么他们可以编写调用一个辅助类is_ok析构函数,并把那在你的对象.如果你认为你的类可以使用相同的异常做一些有用的事情,那么除了在析构函数中忽略它之外你可以做一些事情,但你仍然不应该让它离开析构函数.