The std::allocator_traits template defines a few constants, like propagate_on_container_copy/move_assign to let other containers know whether they should copy the allocator of a second container during a copy or move operation.
We also have propagate_on_container_swap, which specifies whether the allocator should be copied during a swap operation.
Is it really necessary for an Allocator Aware container to check for allocator_traits<A>::propagate_on_container_swap in Container::swap()? Usually, I implement swap as follows:
Container::swap(Container& other)
{
Container tmp(std::move(other));
other = std::move(*this);
*this = std::move(tmp);
}
In other words, I simply implement swap in terms of move assignment. Since the move assignment operation already has to deal with allocator awareness (by checking propagate_on_container_move_assign), is it okay to implement Container::swap() like this, instead of writing a totally different swap function which explicitly checks for propagate_on_container_swap?
It is important to draw a distinction between requirements the standard places on your code vs the requirements it places on types provided by the implementor of the std::lib.
The container requirements specify how the std::containers must behave. However you are free to write your container however you like. Unless you input your container into std::code which requires the std::container behavior, you are good to go. There are just a couple of such places. For example if you adapt your Container with std::stack, then you will have to provide standard behavior if you expect the std::stack to behave according to the standard.
Getting back to your question, if you desire your Container to have the same behavior as one of the std::containers in this regard, then you will have to check and abide by all of the propagate_on traits, and all of the other allocator requirements. This is a non-trivial task. And I am not necessarily recommending it.
The std::containers will not perform a container move construction nor move assignment during swap. They will instead swap their internal representations. They will decide (at compile-time) based on propagate_on_container_swap whether or not they will swap allocators.
If propagate_on_container_swap is true, they will swap allocators and internal representations. Also in this case, swap will be noexcept in C++1z (we hope that is C++17) and forward.
If propagate_on_container_swap is false the allocators shall not be swapped, and need not even be Swappable. However the container internals are still swapped. In this case, if the two allocators do not compare equal, the behavior is undefined.
If you keep your Container::swap as it is, and create a std::stack based on your Container, the std::stack::swap will not have standard behavior. However, it will have the behavior of your Container::swap, and if that is fine with the client of said std::stack and whatever allocator they may be using, then no harm done. std::stack::swap will not behave in mysterious ways just because you didn't rigorously follow all of the intricate details for allocators that the std::containers are required to.
Sure, you can implement it using move-ctor and move-assignment of the container.
That's the way std::swap does it though, and there is absolutely no reason to re-write it instead of using the standard one.
Those propagate-constants allow omitting useless extra-work, and you are free t ignore the potential for optimization.
Just be aware that you are potentially doing too much work, aka your swap is potentially less efficient.
Related
I was writing some code that has a generic container that requires elements to be nothrow_move_constructible.
I decided to add a static_assert that enforces this, just in case.
To my surprise I can't compile now when using boost::container::flat_set.
I assumed that this was just an oversight and I need a more recent boost verison, but it seems that actually they deliberately made it not safely movable:
See docs here:
http://www.boost.org/doc/libs/1_61_0/doc/html/boost/container/flat_set.html
You can see that they did update it to use R-value references and marked swap as noexcept, but they chose not to make the move ctor noexcept.
It appears that move assignment is conditionally noexcept. The condition appears to depend on the value type and on the allocator in some way.
What could be the rationale for not being nothrow move constructible? Is it just an oversight?
If the objects within a container are not nothrow_move_constructible then it is very dangerous to take an entire set of one container and relocate it to another under certain conditions (usually involving Allocators). If two containers were not constructed with the same allocator, then it is no longer safe to move memory from one container to another (think two containers from two different memory arenas).
Digging in to the current source, both the contract and the implementation are problematic:
//! <b>Effects</b>: Move constructs a flat_map.
//! Constructs *this using x's resources.
flat_map(BOOST_RV_REF(flat_map) x)
: m_flat_tree(boost::move(x.m_flat_tree))
{ ... }
So your current expectation that it could currently by nothrow is correct. But what they are doing is probably not right.
I can only guess that they are worried that they will have to do revisit this in the future and don't want to have to weaken the nothrow contract later.
When elements are default-inserted into an instance of std::vector<T>, they are value-initialized by default. I often work with multi-threaded high-performance codes, where such value-initialization might for large arrays represent an unacceptable sequential bottleneck.
The typical approach based on reserve() and push_back()/emplace_back() is of no use if in concurrent codes. I usually end up with one of the following options:
definition of an empty default constructor for T,
definition and usage of a custom allocator with empty construct() member function.
However, both solutions are far from being elegant and also have drawbacks. The former cannot be used for T being a POD type, such as double. The latter requires a given implementation of the C++ Standard Library to supoort the relatively new DefaultInsertable concept. Moreover, definition of a custom allocator is quite tedious.
Is there any chance that in the future of C++ there will be some straightforward way how to "turn off" this default-insertion/value-initialization?
UPDATE
Mayebe, I should've asked simply if it will be possible to avoid zero-initialization of default-inserted elements of a vector for arithmetic types.
Vector is poorly suited to your needs. It supports resizing and accidental copy, neither of which make sense in a multi-threaded environment.
Write a simple container:
template<class T,class Storage=std::aligned_storage_t<sizeof(T),alignof(T)>{
struct buffer{
static_assert(std::is_pod<T)::value, "pod only");
std::size_t count;
std::unique_ptr<Storage[]> storage;
};
Populate it with container-esque begin/end/front/size/[]/empty etc.
Make it move only.
Use rule of zero (with =default).
Give it a explicit buffer(std::size_t) ctor that creates its content uninitialized.
Mix in some span/array_view types, and this should be suitable for your needs.
Maybe have emplace(size_t,Args&&) which does placement new for you with {}.
I'm developing a container-like class and I would like to make use of the standard allocator infrastructure much like the standard containers. On the net I find a lot of material about how to use the std::allocator class alone, or how to define a custom allocator for standard containers, but the material about how to make generically use of an standard conforming allocator is very rare, in particular in the context of C++11, where things seem to be much easier from the point of view of who writes a custom allocator, but more complex from the container's point of view.
So my question is about how to correctly make use of a standard conforming allocator in the most generic way, specifically:
First of all, when should I design a custom container in this way? Is there a sensible performance overhead (including missing optimization opportunities) in using the default allocator instead of plain new/delete?
Do I have to explicitly call contained objects' destructors?
How do I discriminate between stateful and stateless allocators?
How to handle stateful allocators?
When (if ever) are two instances interchangeable (when can I destroy with one instance the memory allocated with another one)?
They have to be copied when the container is copied?
They can/have to be moved when the container is moved?
In the container's move constructor and move assignment operator, when can I move the pointer to allocated memory, and when do I have to allocate different memory and move the elements instead?
Are there issues about exception safety in this context?
I'm specifically interested in an answer about the C++11 world (does it change anything in C++14?)
In all answers below, I'm assuming you want to follow the rules for C++11 std-defined containers. The standard does not require you to write your custom containers this way.
First of all, when should I design a custom container in this way? Is there a sensible performance overhead (including missing
optimization opportunities) in using the default allocator instead of
plain new/delete?
One of the most common and effective uses for custom allocators is to have it allocate off of the stack, for performance reasons. If your custom container can not accept such an allocator, then your clients will not be able to perform such an optimization.
Do I have to explicitly call contained objects' destructors?
You have to explicitly call allocator_traits<allocator_type>::destroy(alloc, ptr), which in turn will either directly call the value_type's destructor, or will call the destroy member of the allocator_type.
How do I discriminate between stateful and stateless allocators?
I would not bother. Just assume the allocator is stateful.
How to handle stateful allocators?
Follow the rules laid out in C++11 very carefully. Especially those for allocator-aware containers specified in [container.requirements.general]. The rules are too numerous to list here. However I'm happy to answer specific questions on any of those rules. But step one is get a copy of the standard, and read it, at least the container requirements sections. I recommend the latest C++14 working draft for this purpose.
When (if ever) are two instances interchangeable (when can I destroy with one instance the memory allocated with another one)?
If two allocators compare equal, then either can deallocate pointers allocated from the other. Copies (either by copy construction or copy assignment) are required to compare equal.
They have to be copied when the container is copied?
Search the standard for propagate_on and select_on_container_copy_construction for the nitty gritty details. The nutshell answer is "it depends."
They can/have to be moved when the container is moved?
Have to be for move construction. Move assignment depends on propagate_on_container_move_assignment.
In the container's move constructor and move assignment operator, when can I move the pointer to allocated memory, and when do I have to allocate different memory
and move the elements instead?
The newly move constructed container should have gotten its allocator by move constructing the rhs's allocator. These two allocators are required to compare equal. So you can transfer memory ownership for all allocated memory for which your container has a valid state for that pointer being nullptr in the rhs.
The move assignment operator is arguably the most complicated: The behavior depends on propagate_on_container_move_assignment and whether or not the two allocators compare equal. A more complete description is below in my "allocator cheat sheet."
Are there issues about exception safety in this context?
Yes, tons. [allocator.requirements] lists the allocator requirements, which the container can depend on. This includes which operations can and can not throw.
You will also need to deal with the possibility that the allocator's pointer is not actually a value_type*. [allocator.requirements] is also the place to look for these details.
Good luck. This is not a beginner project. If you have more specific questions, post them to SO. To get started, go straight to the standard. I am not aware of any other authoritative source on the subject.
Here is a cheat-sheet I made for myself which describes allocator behavior, and the container's special members. It is written in English, not standard-eze. If you find any discrepancies between my cheat sheet, and the C++14 working draft, trust the working draft. One known discrepancy is that I've added noexcept specs in ways the standard has not.
Allocator behavior:
C() noexcept(is_nothrow_default_constructible<allocator_type>::value);
C(const C& c);
Gets allocator from alloc_traits::select_on_container_copy_construction(c).
C(const C& c, const allocator_type& a);
Gets allocator from a.
C(C&& c)
noexcept(is_nothrow_move_constructible<allocator_type>::value && ...);
Gets allocator from move(c.get_allocator()), transfers resources.
C(C&& c, const allocator_type& a);
Gets allocator from a. Transfers resources if a == c.get_allocator().
Move constructs from each c[i] if a != c.get_allocator().
C& operator=(const C& c);
If alloc_traits::propagate_on_container_copy_assignment::value is true,
copy assigns allocators. In this case, if allocators are not equal prior
to assignment, dumps all resources from *this.
C& operator=(C&& c)
noexcept(
allocator_type::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
If alloc_traits::propagate_on_container_move_assignment::value is true,
dumps resources, move assigns allocators, and transfers resources from c.
If alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() == c.get_allocator(), dumps resources, and transfers
resources from c.
If alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() != c.get_allocator(), move assigns each c[i].
void swap(C& c)
noexcept(!allocator_type::propagate_on_container_swap::value ||
__is_nothrow_swappable<allocator_type>::value);
If alloc_traits::propagate_on_container_swap::value is true, swaps
allocators. Regardless, swaps resources. Undefined behavior if the
allocators are unequal and propagate_on_container_swap::value is false.
I've read today that you should not use the STL containers for auto_ptr because of
the fact that the auto_ptr deletes it rhs value in the = operator.
So i have 2 question :
1) Does this mean that all classes that have this behavior should not be used in containers?
2) what sort of containers can you use?
1) Does this mean that all classes that have this behavior should not
be used in containers?
Yes indeed, because that is not correct copying behaviour, since the copy is not equal to the source afterwards but destroys the source. This is kind of a broken implementation of move-semantics before C++11, required for the strict unique ownership semantics of std::auto_ptr.
2) what sort of containers can you use?
The real answer is actually, that classes having this behaviour (a copy constructor/assignment destroying its source) should just not exist. And fortunately this is not needed anymore nowadays, since C++11 has proper move-semantics, which realize exactly this destructive copy but in a safe way (simply said, only when the source is really not needed anymore).
Therefore std::auto_ptr is deprecated and should not be used anymore. It has been replaced by std::unique_ptr which is movable but not copyable. But since C++11 containers rather move their elements than copy when appropriate, a std::unique_ptr can be used perfectly inside of standard containers. You just cannot copy the container or fill it with a single object which would require copies of std::unique_ptrs, but those operations should not work anyway, since they are conceptually wrong for unique ownership semantics.
And as a side note, if you actually chose std:auto_ptr for a reason, that is you want unique ownership semantics, then a std::shared_ptr (as suggested by other answers) is plain wrong since it exhibits shared ownership. std::unique_ptr is today's std::auto_ptr. Never spam std::shared_ptrs where std::unique_ptrs (or even raw pointers, but from your question I rule that option out) are appropriate.
Auto pointer has a very strict ownership: it and only it is responsible for the lifetime of the object it points at. If you copy an auto_ptr, you lose the reference to what it pointed at.
The problem is in the way STL containers do their stuff. For example, when you are adding an element, the container might expand to get more memory, which leads to copying all the values to the new memory, which, itself, leads to losing the auto_ptrs.
I think that associative containers might not copy themselves entirely when they allocate additional memory, but I'm absolutely not sure, if someone can confirm it, please post a comment, or just edit my answer. Anyway, you'd better not risk it.
Also note that Auto_ptr is deprecated since C++0x, it is advised to use unique_ptr instead. In your case, std::shared_ptr will probably do the trick, unless you really need unique ownership for those objects of yours.
Exactly.
In general, sequence container elements must be CopyConstructible and Assignable. This means that they require:
public copy constructor
public assignment operator
Asociative containers (set<> and map<>) also must provide strict weak ordering, i.e. operator < must be defined (or a dedicated comparison function).
Chapter 23.1 of C++ standard provides detailed requirements.
I am aware that you have to be careful with auto pointers (and why), especially with the STL. But I don't see a problem with this:
std::map<T1, std::auto_ptr<T2> >;
Is this safe?
I see how it would break in an std::vector, because it has to copy its items from time to time, but is this also true for the value type of an std::map?
Edit: Apparently, regardless whether it's safe, I cannot (technically) populate the map, but I'll leave the question open for theoretical considerations. Otherwise, consider it closed.
It's not safe. Technically, std::auto_ptr doesn't meet the requirement of CopyConstructible or Assignable because copies made using auto_ptr's copy constructor or copy assignment operator aren't equivalent to the source of the copy after the copy operation. These requirements must be met for any type used with any standard container.
You may find that you appear to get the behaviour you expect on one implementation for one particular use case but if you violate the requirements of the container you can't expect your code to work in all situations.
Still not safe. If nothing else, the first time you retrieved the pointer from the map you would transfer ownership and make it invalid.
There is an extremely narrow valid use case for auto_ptr. The only one I can remember coming across is when an object wants to make it explicitly clear that it takes ownership and responsibility for a pointer that you hand it.