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.
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.
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:
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With