在堆上分配异常有什么陷阱吗?
一个明显的缺陷是堆分配可能会失败。
有趣的是,当抛出异常时,它实际上会抛出异常对象的副本,该对象是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<> 是否是个好主意。这是因为复制 auto_ptr<> 会破坏原始异常,因此在像 catch(std::auto_ptr<std::exception> e) 中按值捕获后,后续 throw; 没有参数重新抛出原始异常可能会抛出 NULL auto_ptr<>,因为它被复制了来自(我没有尝试过)。
出于这个原因,我可能会抛出一个普通指针,例如 throw new my_exception(...) 并按值捕获它并手动 delete 它。因为手动内存管理会导致内存泄漏,所以我会创建一个小型库来在线程之间传输异常并将此类低级代码放入其中,这样其余代码就不必担心内存管理问题。
另一个问题是需要特殊的 throw 语法,例如 throw new exception(...),可能有点过于侵入性,也就是说,可能存在无法更改的现有代码或第三方库以标准方式抛出喜欢throw exception(...)。坚持标准的 throw 语法并在一个顶级线程捕获块,复制该异常并在另一个线程中重新抛出副本(可能在join 或提取另一个线程结果的函数中,尽管抛出的线程可能是独立的并且不会产生完全没有结果,但这完全是另一个问题,我们假设我们处理的是某种生命周期有限的工作线程)。这样,另一个线程中的异常处理程序可以通过引用或值以标准方式捕获异常,而无需处理堆。我可能会选择这条路。
你也可以看看Boost: Transporting of Exceptions Between Threads。