【问题标题】:Is it appropriate to throw from destructor in this case在这种情况下从析构函数中抛出是否合适
【发布时间】:2012-10-10 00:56:14
【问题描述】:

我有一个类将用户提供的工作分配给多个线程。类似(简化):

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 退出的异常都意味着工作已失败。投掷或不投掷析构函数并不是行为的实质性改变。
  • 析构函数的后置条件不仅是资源清理,而且工作已经成功完成。因此,例外应该是适当的。

问题

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

【问题讨论】:

  • 使用回调函数?

标签: c++ exception destructor


【解决方案1】:

不,不要从析构函数中抛出。致电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 如果异常逃脱了作为堆栈展开的一部分调用的析构函数,则调用它,但不会仅仅因为析构函数在展开期间抛出并捕获异常。

如果调用者认为他们可以对析构函数中抛出的异常做一些有用的事情,那么他们可以编写一个辅助类,在 its 析构函数中调用is_ok,并将其放在您的对象上。如果你认为你的类可以用同样的异常做一些有用的事情,那么你可以做一些事情,而不仅仅是在你的析构函数中忽略它,但你仍然不应该让它离开析构函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-12
    • 2010-11-21
    • 2011-06-16
    • 2012-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多