On Tue, Aug 25, 2015, [Matt] wrote:
Regarding your use of allocators in your implementation: a. Support the C++ allocator model 1. Either use a.allocate(size) or allocator_traits<A>::allocate(a, size, hint) if you want to supply a hint, but don't bother calling a.allocate(size, 0) - you alienate anyone who writes a C++11 allocator that may not provide an allocate(size, hint) member.
Right - this might be something that has to change for the boost submission, the original is designed to work with both C++0x and C++11, and the hints made a substantial difference to speed with std::allocator.
Hints are fine. But use them with std::allocator_traits::allocate or boost::allocator_traits::allocate.
2. Use allocator_traits<A>::construct(a, ...) and allocator_traits<A>::destroy(a) instead of a.construct(...) and a.destroy(...)
Why, and is there a C++0x-friendly way of doing the same thing?
std::allocator_traits is the C++11 and above way of doing things.
3. Use allocator_traits<A> to obtain the type members (pointer, rebind, etc.) (You could use boost::allocator_traits if you want to support pre-C++11)
What is the advantage of doing this?
The motivation for using std::allocator_traits in all of these 3 points (a.1-a.3) is to support the C++11 allocator model. The C++ standard library does this. Most Boost libraries also do this.
b. Use a compressed_pair<> or the EBCO to reduce the storage for when stateless allocators are supplied (i.e. instead of the 1 + 1 bytes that would be used for the empty objects).
Empty base optimization is not something I understand at this point. I've read a bit about it but I am not sure in which particular area of the colony it would make a difference in?
When someone supplies a stateless allocator, like std::allocator<T>, the sizeof(allocator) is still 1 and thus the size of your container object is increased by 1 when it doesn't need to be. The EBCO (or compressed_pair) can be used here to make sure that empty allocators do not add the extra +1 byte to your container object storage. This is also a commonly used technique in C++ standard library implementations, as well as in many Boost libraries that support custom allocators and custom deleters.
c. Support element_type's whose constructor might throw: Do you catch, accordingly destroy any constructed elements, deallocate any allocated elements, before rethrowing?
I guess that makes sense in the limited scenario where memory is full.
No. We're not talking about allocate() throwing (which could happen
because of memory being full, or just some constraints a particular
generic custom allocator may have that are violated). We're talking
about construct() throwing. Both C++ standard library containers, like
std:;vector<T>, and containers in Boost libraries, must acknowledge
the fact that constructing a T object (i.e. ::new(p) T(...) or
std::allocator_traits
My general feeling is more in line with Mike Acton's in that if something throws, you've got a problem anyway - peaceably unrolling isn't really helping anyone.
You're actually misunderstanding Mike completely, and out of context. Writing generic code around T (value type) or A (allocator type) requires that you handle things like this. As I mentioned, the C++ standard library containers do this. So do Boost library containers. If you do things like both allocate and construct, and construction throws, and the user of your library has no way to deallocate what you've allocated, it completely alienates them using your container in any of their generic code where they support T types whose constructors can throw, etc. Best, Glen