【问题标题】:Any pitfalls with allocating exceptions on the heap?在堆上分配异常有什么陷阱吗?
【发布时间】:2011-10-07 17:01:09
【问题描述】:

问题说明了一切:在堆上分配异常有什么陷阱吗?

我问是因为在堆上分配异常,结合polymorphic exception idiom,解决了线程之间传输异常的问题(为了讨论,假设我不能使用exception_ptr)。或者至少我认为确实如此......

我的一些想法:

  • 异常处理程序必须捕获异常并知道如何删除它。这可以通过使用适当的删除器实际抛出 auto_ptr 来解决。
  • 还有其他方法可以跨线程传输异常吗?

【问题讨论】:

  • 我不会使用bad_alloc

标签: c++ exception exception-handling


【解决方案1】:

在堆上分配异常有什么陷阱吗?

一个明显的缺陷是堆分配可能会失败。

有趣的是,当抛出异常时,它实际上会抛出异常对象的副本,该对象是throw 的参数。使用 gcc 时,it creates that copy in the heap 但有所不同。如果堆分配失败,它将使用静态紧急缓冲区而不是堆:

extern "C" void *
__cxxabiv1::__cxa_allocate_exception(std::size_t thrown_size) throw()
{
    void *ret;

    thrown_size += sizeof (__cxa_refcounted_exception);
    ret = malloc (thrown_size);

    if (! ret)
    {
        __gnu_cxx::__scoped_lock sentry(emergency_mutex);

        bitmask_type used = emergency_used;
        unsigned int which = 0;

        if (thrown_size > EMERGENCY_OBJ_SIZE)
            goto failed;
        while (used & 1)
        {
            used >>= 1;
            if (++which >= EMERGENCY_OBJ_COUNT)
                goto failed;
        }

        emergency_used |= (bitmask_type)1 << which;
        ret = &emergency_buffer[which][0];

    failed:;

        if (!ret)
            std::terminate ();
    }
}

因此,一种可能性是复制此功能以防止异常的堆分配失败。

异常处理程序必须捕获异常并知道如何删除它。这可以通过使用适当的删除器实际抛出 auto_ptr 来解决。

不确定使用auto_ptr&lt;&gt; 是否是个好主意。这是因为复制 auto_ptr&lt;&gt; 会破坏原始异常,因此在像 catch(std::auto_ptr&lt;std::exception&gt; e) 中按值捕获后,后续 throw; 没有参数重新抛出原始异常可能会抛出 NULL auto_ptr&lt;&gt;,因为它被复制了来自(我没有尝试过)。

出于这个原因,我可能会抛出一个普通指针,例如 throw new my_exception(...) 并按值捕获它并手动 delete 它。因为手动内存管理会导致内存泄漏,所以我会创建一个小型库来在线程之间传输异常并将此类低级代码放入其中,这样其余代码就不必担心内存管理问题。

另一个问题是需要特殊的 throw 语法,例如 throw new exception(...),可能有点过于侵入性,也就是说,可能存在无法更改的现有代码或第三方库以标准方式抛出喜欢throw exception(...)。坚持标准的 throw 语法并在一个顶级线程捕获块,复制该异常并在另一个线程中重新抛出副本(可能在join 或提取另一个线程结果的函数中,尽管抛出的线程可能是独立的并且不会产生完全没有结果,但这完全是另一个问题,我们假设我们处理的是某种生命周期有限的工作线程)。这样,另一个线程中的异常处理程序可以通过引用或值以标准方式捕获异常,而无需处理堆。我可能会选择这条路。

你也可以看看Boost: Transporting of Exceptions Between Threads

【讨论】:

    【解决方案2】:

    有两个明显的一个: 一个 - 简单 - 是做 throw new myexception 风险抛出 bad_alloc(不是 bad_alloc*),因此 catch exception* 不会捕获最终不可能分配异常。并且抛出 new(nothrow) myexception 可能会抛出......一个空指针。

    另一个 - 更多的是设计问题 - 是“谁必须抓住”。如果不是您自己,请考虑您的客户(也可能是其他人的客户)具有 - 取决于谁投掷 - 决定是否删除的情况。可能会造成混乱。

    解决问题的典型方法是通过引用(或地址)抛出一个静态变量:在展开堆栈中向下时不需要删除,也不需要复制

    【讨论】:

      【解决方案3】:

      如果您引发异常的原因是堆存在问题(内存不足或其他),那么在堆上分配您的异常只会导致更多问题。

      【讨论】:

        【解决方案4】:

        在抛出std::auto_ptr&lt;SomeException&gt; 时还有另一个问题,并且还会提升共享指针:虽然runtime_error 是从exception 派生的,但auto_ptr&lt;runtime_error&gt; 不是从auto_ptr&lt;exception&gt; 派生的。

        因此,catch(auto_ptr&lt;exception&gt; &amp;) 不会捕获 auto_ptr&lt;runtime_error&gt;

        【讨论】:

          猜你喜欢
          • 2011-06-28
          • 1970-01-01
          • 1970-01-01
          • 2011-12-13
          • 2014-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-03-06
          相关资源
          最近更新 更多