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

时间:2012-10-19 11:55:19

标签: c++ exception destructor

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

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的任何异常都表示工作失败。投掷或不投掷析构函数并不是行为的重大改变。
  • 析构函数的后置条件不仅是资源清理,而且工作已成功完成。因此,例外应该是适当的。

问题

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

1 个答案:

答案 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,并将其放在你的宾语。如果你认为你的类可以使用相同的异常做一些有用的事情,那么除了在析构函数中忽略它之外你可以做一些事情,但你仍然不应该让它离开析构函数。