标准库(STL)容器是否支持一种nothrow分配形式?

时间:2011-01-28 09:53:38

标签: c++ stl standard-library

new运算符(或者对于POD,malloc / calloc)在分配大块内存时支持一种简单有效的失败形式。

说我们有这个:

const size_t sz = GetPotentiallyLargeBufferSize(); // 1M - 1000M
T* p = new (nothrow) T[sz];
if(!p) {
  return sorry_not_enough_mem_would_you_like_to_try_again;
}
...

std :: containers有没有这样的构造,或者我总是要与std::vector和朋友一起处理(预期!!)异常?


是否可能有一种方法可以编写一个自定义分配器来预分配内存,然后将这个自定义分配器传递给向量,这样只要向量不要求比预先放入分配器的内存更多的内存,就可以了会不会失败?


事后补充:除了正常的预留功能外,真正需要的是成员函数bool std::vector::reserve(std::nothrow) {...}。但是,既然只有扩展分配器以允许不进行分配,那只会是有意义的,它就不会发生。似乎(nothrow)new毕竟是有益的: - )


编辑:关于为什么我甚至会问这个:

我在调试时考虑了这个问题(调试器的第一次机会/第二次机会异常处理):如果我将调试器设置为1st-chance catch任何bad_alloc,因为我正在测试低内存条件,它会如果它还捕获了那些已经很好预期并在代码中处理的bad_alloc异常,那就太烦人了。这不是/不是一个非常大的问题,但我刚刚想到,布道认为异常是针对特殊情况的,而且我已经预期在代码中每次奇怪的调用都会发生这种情况并不例外。

如果new (nothrow)有合法用途,那么vector-nothrow-reserve也会有。

3 个答案:

答案 0 :(得分:15)

默认情况下,标准STL容器类使用引擎盖下的std::allocator类进行分配,这就是为什么如果没有可用内存它们可以抛出std::bad_alloc的原因。有趣的是,分配器上的C ++ ISO规范声明任何分配器类型的返回值必须是指向能够容纳一定数量元素的内存块的指针,这会自动阻止您构建自定义分配器可能会使用nothrow new版本的{{1}}来进行这类静默分配失败。但是,如果没有可用的内存,你可以构建一个终止程序的自定义分配器,因为当没有内存时,返回的内存是有效的。 : - )

简而言之,标准容器默认情况下会抛出异常,并且您可能尝试使用自定义分配器自定义它们以防止抛出异常的任何方式都不符合规范。

答案 1 :(得分:6)

我们经常听到“我不想使用例外,因为它们效率低下”。

除非您指的是希望关闭所有运行时类型信息的“嵌入式”环境,否则如果以适当的方式抛出异常,则不应过多担心异常的低效率。内存不足是这些适当的方式之一。

向量合约的一部分是,如果无法分配,它将抛出。如果你编写一个返回NULL而不是更糟糕的自定义分配器,因为它会导致未定义的行为。

如果你必须使用一个分配器,它将首先尝试一个失败分配回调(如果有的话),并且只有当你仍然不能分配到throw时,你仍然必须得到一个例外。

我可以给你一个提示:如果你真的要分配如此大量的数据,那么vector可能是错误的类使用,你应该使用std :: deque。为什么?因为deque不需要连续的内存块,但仍然是恒定的时间查找。优势有两方面:

    • 分配失败的次数会减少。因为你不需要一个连续的块,所以你可能有可用的内存,尽管不是在一个块中。
    • 没有重新分配,只有更多的分配。重新分配是昂贵的,因为它需要移动所有对象。当您处于高音量模式时,可以非常及时地进行操作。
  1. 当我在过去使用这样一个系统时,我们发现我们实际上可以使用deque存储超过4倍的数据因为我们可以使用向量因为上面的原因1,并且由于原因2它更快。 / p>

    我们做的其他事情是分配一个2MB的备用缓冲区,当我们捕获一个bad_alloc时,我们释放了缓冲区然后扔了以显示我们已达到容量。但是现在有2MB备用,我们至少知道我们有内存来执行小操作,将数据从内存移动到临时磁盘存储。

    因此我们有时可以捕获bad_alloc并采取适当的操作来保持一致状态,这是异常的目的,而不是假设内存耗尽总是致命的,除了终止程序之外永远不应该做任何事情(甚至更糟糕的是,调用未定义的行为。)

答案 2 :(得分:2)

标准容器为此使用异常,除了一旦您知道它将成功后再尝试分配,您就无法绕过它。您无法移植,因为实现通常会以未指定的数量进行过度分配。如果您必须在编译器中禁用异常,那么您在使用容器时可能会受到限制。

关于“简单而有效”,我认为std容器相当简单且合理有效:

T* p = new (nothrow) T[sz];
if(!p) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}
... more code that doesn't throw ...
delete[] p;

try {
    std::vector<T> p(sz);
    ... more code that doesn't throw ...
} catch (std::bad_alloc) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}

这是相同数量的代码行。如果它在故障情况下出现效率问题,那么您的程序必须每秒失败数十万次,在这种情况下,我稍微质疑程序设计。我还想知道在什么情况下抛出和捕获异常的成本与new可能确定它不能满足请求的系统调用的成本相比是显着的。

但更好的是,如何编写API以使用异常:

std::vector<T> p(sz);
... more code that doesn't throw ...

比原始代码短四行,当前必须处理“sorry_not_enough_mem_would_you_like_to_try_again”的调用者可以改为处理异常。如果此错误代码通过多层调用者传递,则可以在每个级别保存四行。 C ++有例外,几乎所有目的都可以接受,并相应地编写代码。

关于“(预期!!)” - 有时您知道如何处理错误情况。在这种情况下要做的是捕获异常。这是异常应该如何工作。如果抛出异常的代码以某种方式知道任何人都没有抓到它,那么它可能会终止程序。