Do Standard Library (STL) Containers support a form of nothrow allocation?

佐手、 提交于 2020-01-22 13:31:45

问题


The new operator (or for PODs, malloc/calloc) support a simple and efficient form of failing when allocating large chunks of memory.

Say we have this:

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;
}
...

Is there any such construct for the std::containers, or will I always have to handle an (expected!!) exception with std::vector and friends?


Would there maybe be a way to write a custom allocator that preallocates the memory and then pass this custom allocator to the vector, so that as long as the vector does not ask for more memory than you put into the allocator beforehand, it will not fail?


Afterthought: What really would be needed were a member function bool std::vector::reserve(std::nothrow) {...} in addition to the normal reserve function. But since that would only make sense if allocators were extended too to allow for nothrow allocation, it just won't happen. Seems (nothrow) new is good for something after all :-)


Edit: As to why I'm even asking this:

I thought of this question while debugging (1st chance / 2nd chance exception handling of the debugger): If I've set my debugger to 1st-chance catch any bad_alloc because I'm testing for low-memory conditions, it would be annoying if it also caught those bad_alloc exceptions that are already well-expected and handled in the code. It wasn't/isn't a really big problem but it just occurred to me that the sermon goes that exceptions are for exceptional circumstances, and something I already expect to happen every odd call in the code is not exceptional.

If new (nothrow) has it's legitimate uses, the a vector-nothrow-reserve also would have.


回答1:


By default, the standard STL container classes use the std::allocator class under the hood to do their allocation, which is why they can throw std::bad_alloc if there's no memory available. Interestingly, the C++ ISO specification on allocators states that the return value of any allocator type must be a pointer to a block of memory capable of holding some number of elements, which automatically precludes you from building a custom allocator that could potentially use the nothrow version of new to have these sorts of silent allocation failures. You could, however, build a custom allocator that terminated the program if no memory was available, since then it's vacuously true that the returned memory is valid when no memory is left. :-)

In short, the standard containers throw exceptions by default, and any way you might try to customize them with a custom allocator to prevent exceptions from being thrown won't conform to the spec.




回答2:


Too often we hear "I don't want to use exceptions because they are inefficient".

Unless you are referring to an "embedded" environment where you want all runtime type information switched off, you should not be worrying too much about inefficiency of exceptions if they are being thrown in an appropriate way. Running out of memory is one of these appropriate ways.

Part of the contract of vector is that it will throw if it cannot allocate. If you write a custom allocator that returned NULL instead that would be worse, as it would cause undefined behaviour.

If you have to then use an allocator that will first attempt a failed-allocation callback if one is available, and only then if you still cannot allocate to throw, but still you have to end up with an exception.

Can I give you a hint though: if you really are allocating such large amounts of data then vector is probably the wrong class to use and you should use std::deque instead. Why? Because deque does not require a contiguous block of memory but is still constant time lookup. And the advantages are two-fold:

    • Allocations will fail less frequently. Because you do not need a contiguous block so you may well have the memory available albeit not in a single block.
    • There is no reallocation, just more allocations. Reallocations are expensive as it requires all your objects to be moved. When you are in high-volume mode that can be a very timely operation.

When I worked on such a system in the past we found we could actually stored over 4 times as much data using deque as we could using vector because of the reason 1 above, and it was faster because of the reason 2.

Something else we did was allocate a 2MB spare buffer and when we caught a bad_alloc we freed the buffer and then threw anyway to show we had reached capacity. But with 2MB spare now we at least knew we had the memory to perform small operations to move the data from memory to temporary disk storage.

Thus we could catch the bad_alloc sometimes and take an appropriate action retaining a consistent state, which is the purpose of exceptions rather than assuming that running out of memory is always fatal and should never do anything other than terminate the program (or even worse, invoke undefined behaviour).




回答3:


Standard containers use exceptions for this, you can't get around it other than attempting the allocation only once you know it will succeed. You can't do that portably, because the implementation will typically over-allocate by an unspecified amount. If you have to disable exceptions in the compiler then you're limited in what you can do with containers.

Regarding "simple and efficient", I think that the std containers are reasonably simple and reasonably efficient:

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;
}

It's the same number of lines of code. If it presents an efficiency problem in the failure case then your program must be failing hundreds of thousands of times per second, in which case I slightly question the program design. I also wonder under what circumstances the cost of throwing and catching an exception is significant compared with the cost of the system call that new probably makes to establish that it can't satisfy the request.

But even better, how about writing your APIs to use exceptions too:

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

Four lines shorter than your original code, and the caller who currently has to handle "sorry_not_enough_mem_would_you_like_to_try_again" can instead handle the exception. If this error code is passed up through several layers of callers, you might save four lines at each level. C++ has exceptions, and for almost all purposes you may as well accept this and write code accordingly.

Regarding "(expected!!)" - sometimes you know how to handle an error condition. The thing to do in that case is to catch the exception. It's how exceptions are supposed to work. If the code that throws the exception somehow knew that there was no point anyone ever catching it, then it could terminate the program instead.



来源:https://stackoverflow.com/questions/4826838/do-standard-library-stl-containers-support-a-form-of-nothrow-allocation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!