I am getting a crash when trying to move a std::vector<T> where T is clearly not movable (no move constructor/assignment operator was defined, and it contains internal pointers)
But why would the move functions of vector want to call the move functions of T? It should not be necessary.
So my question from the title: Is a std::vector<T> movable if T is not movable?
Yes, std::vector<T> is movable even if T is not movable. The left side merely takes ownership from the vector on the right, no elements are touched. (With one exception, listed in #2)
The move assignment of a vector would only call the move constructor or move assignment of T if the and their allocators compare as equal and the left side's allocator's propagate_on_container_move_assignment is false. (Note that if your move constructor might throw or doesn't exist, the copy constructor will be used instead) However, it is unlikely that you are encountering either of these.
Reworded: if propagate_on_container_move_assignment is true (it usually is), then the vector is always movable, and does so without touching individual elements. If it's false, but the allocators compare equal, the vector will be moved without touching individual elements. If it's false and the allocators compare inequal, the individual elements will be transferred. If a nothrow move assignment exists, that's used. Otherwise, if a copy constructor exists, that's used. Otherwise the throwing move assignment is used. If it's neither movable nor copiable, then that's undefined behavior.
Having no move assignment operator defined, and the class containing internal pointers, does not mean your class is not movable. In fact, it sounds like the compiler thinks it is movable. If you want to disable moves, use T(T&&) = delete; and T& operator=(T&&) =delete, but I don't recommend that. Instead, add a correctly working move constructor and move assignemnt. They tend to be easy, and quite useful.
Table 99 in [container.requirements.general] of n3797 about "Allocator-aware container requirements" says about move construction:
Requires: move construction of A shall not exit via an exception.
Where A is the allocator type. It does not require MoveInsertable for the value type.
The time complexity requirement is: "constant", btw.
std::vector typically stores some pointers plus the allocator. When move-constructing a std::vector, only pointers and the allocator have to be moved. The elements don't need to be touched.
Related
When std::vector reallocate its memory array, what kind of copy / move constructor is used to copy / move elements to new houses?
If the move-constructor exists and is noexcept then it is used. Otherwise the copy-constructor is used.
Using a move-constructor that might throw is undesirable as it might happen that some objects are moved to the new storage and then an exception prevents the rest of the objects being moved.
The cppreference.com site does say that if the object is non-copyable , but has a non-noexcept move constructor, then it will use that move-constructor, with "unspecified behaviour" if an exception is thrown. I guess that means elements may be lost from the vector.
Assuming I have a container, for the sake of argument let's say a vector:
std::vector<T> v;
and assuming my type 'T' has a move ctor:
T(T&& move_from) noexcept //...implementation
However I have no move assignment operator.
Is it possible to move a value to type T into a location at a certain index inside the vector ? Similar to the way I can insert the element in between two elements or at the end by moving it using:
v.emplace(std::move(value));
v.emplace_back(std::move(value));
... The one way everyone seems to suggest replacing a vector's element is by using:
v.at(index) = value;
However what is bound to invoke the copy ctor (or however the opearator= copy is canonically called).
It has come to my mind to use std::swap on the element at the desired index and the value I wish to move in, however, it seems to be a bit of a waste since I don't need the value I am replacing anymore AND if I don't have a swap() defined on my class the std::swap seems to just invoke the copy ctor of both elements... which just makes the whole thing twice as expensive.
How should one go about moving an element at an index in a vector and in an stl container in general (if this can be generalized) ? Is there a reason why this shouldn't be doable in the first place ?
Note, I am asking how this is doable on types that have only a standard move ctor defined, I can see it becoming fairly easy if I define a swap() on the types. However, the existence of the swap method doesn't seem to be as common so I'd prefer to avoid that... not to mention that when the method is not available, I'd have to either figure that out with sfinae (which will make code unreadable) or suffer the penalty of a double call to the copy ctor.
v.at(index) = std::move(value) depends on there being a move-assignment operator. The std::swap solution needs a nothrow move-assignment operator.
Also there is definitely nothing bad about defining T::swap, it may be marginally more efficient than falling back to std::swap.
Writing the move-assignment operator for T would be a good solution. If you can't do that then it's possible to create and destroy in-place, e.g.:
T *ptr = &v.at(index);
ptr->~T();
new(ptr) T( std::move(value) );
which relies on T's move-constructor being noexcept, otherwise you are stuffed if it throws.
A good reason to use swap or move-assignment is that it is inherently exception-safe. The move-assignment operator will be beneficial for other container operations too.
The C++11 std::allocator_traits template is used to query an Allocator to determine whether propagate_on_copy_assignment and propagate_on_move_assignment is true. These values affect how Container types must implement copy and move assignment. If std::allocator_traits<Allocator>::propagate_on_move_assignment == true then a Container move assignment operator must move assign its internal allocator object using the allocator contained in the RHS Container object.
Presumably, the point of this is that so we can implement Allocator types that can inform the client Container whether or not a move or copy operation should require that we copy any internal state inside the Allocator.
So... why do these typedefs only apply to assignment. Why do we not have propagate_on_copy_construct and propagate_on_move_construct? If an allocator has some internal state, shouldn't the client Container object be aware of that so it knows whether or not it should copy/move the allocator?
In assignment one has two choices:
Keep your existing allocator.
Copy/move from the rhs allocator.
The propagate_on_assign traits controls the choice for the assignment operators.
For construction, choice 1 (keeping your existing allocator) is not an option. There is no existing allocator. Instead your choices are:
Construct an allocator from nothing.
Copy/move from the rhs allocator.
Copy/move from some other source.
Choice one might be considered: default construct an allocator. Choice 3 is pretty vague, but different allocator schemes can be unpredictable and it is good to give the allocator designer as much flexibility as possible: Possibly to do some things that the committee never anticipated.
In an attempt to satisfy all three choices, at least for the container copy constructor, a new allocator_traits "switch" was designed:
Alloc select_on_container_copy_construction(const Alloc& rhs);
allocator_traits will return rhs. select_on_container_copy_construction() if that expression is well-formed. Otherwise it will return rhs. And container copy constructors are required to use allocator_traits<A>:: select_on_container_copy_construction(a) when constructing the lhs allocator during copy construction.
For allocator designers who just want their allocator copied during copy construction (choice 2), they don't have to do anything.
Allocator designers who want to have the lhs allocator default constructed on container copy construction just need to write the following member function for their allocator:
Alloc select_on_container_copy_construction() const {return Alloc{};}
And if they think of something clever for choice 3, they can probably implement it using the same hook as for choice 1.
So for copy construction, there is no need for a propagate_on trait as the select_on_container_copy_construction already covers all bases.
For move construction, see the bottom of page 12 of N2982:
Note that there is no select_on_container_move_construction()
function. After some consideration, we decided that a move
construction operation for containers must run in constant-time and
not throw, as per issue 1166.
Indeed this paper, and papers preceding it (that it references) are a good source of information for the rationale behind the C++11 allocator design. This paper even references a allocator_propagate_on_copy_construction from a previous paper, which subsequently evolved into select_on_container_copy_construction().
I know the stl priority queue uses make_heap, push_heap, and pop_heap to manage the underlying stl container (a vector, for example).
Are the elements' copy constructors being called when moving elements around during the make_heap, push_heap, and pop_heap calls?
According to the standard, push_heap and make_heap require the value type (*iterator) to be MoveAssignable and MoveConstructible; pop_heap and sort_heap also require the iterator type to be ValueSwappable, which requires swap to work, which also requires the value type to be MoveAssignable and MoveConstructible.
My interpretation is that the standard library can only invoke operations which can be satisfied with move semantics, which could be tested by trying it on a container whose value type's copy constructor and copy assignment operator have been deleted.
By way of a quick verification, I tried the heap functions on a datatype whose move constructor and move assignment operator were deleted. Both gcc 4.7.2 and clang 3.2 generated compile-time errors complaining that the move operations were deleted. The test with deleted copy operations compiled just fine.
I tested my implementation. It uses only move constructors and move assignments. Since the underlying type is typical a vector or a deque, I can't see how you can do any better.
You can test yourself by creating a dummy class with just one int (needed for compairing objects) and putting print statements in the default constructor, the copy constructor, the move constructor, the copy assignment operator and the move assignemt operator.
Although this is implementation-dependant, most sensible implementations will use std::swap to move elements around, therefore avoiding copying overhead.
I noticed that std::string's (really std::basic_string's) move assignment operator is noexcept. That makes sense to me. But then I noticed that none of the standard containers (e.g., std::vector, std::deque, std::list, std::map) declares its move assignment operator noexcept. That makes less sense to me. A std::vector, for example, is typically implemented as three pointers, and pointers can certainly be move-assigned without throwing an exception. Then I thought that maybe the problem is with moving the container's allocator, but std::string's have allocators, too, so if that were the issue, I'd expect it to affect std::string.
So why is std::string's move assignment operator noexcept, yet the move assignment operators for the standard containers are not?
I believe we're looking at a standards defect. The noexcept specification, if it is to be applied to the move assignment operator, is somewhat complicated. And I believe this statement to be true whether we are talking about basic_string or vector.
Based on [container.requirements.general]/p7 my English translation of what a container move assignment operator is supposed to do is:
C& operator=(C&& c)
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].
Notes:
alloc_traits refers to allocator_traits<allocator_type>.
When alloc_traits::propagate_on_container_move_assignment::value is true the move assignment operator can be specified noexcept because all it is going to is deallocate current resources and then pilfer resources from the source. Also in this case, the allocator must also be move assigned, and that move assignment must be noexcept for the container's move assignment to be noexcept.
When alloc_traits::propagate_on_container_move_assignment::value is false, and if the two allocators are equal, then it is going to do the same thing as #2. However one doesn't know if the allocators are equal until run time, so you can't base noexcept on this possibility.
When alloc_traits::propagate_on_container_move_assignment::value is false, and if the two allocators are not equal, then one has to move assign each individual element. This may involve adding capacity or nodes to the target, and thus is intrinsically noexcept(false).
So in summary:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
And I see no dependence on C::value_type in the above spec and so I believe it should apply equally well to std::basic_string despite C++11 specifying otherwise.
Update
In the comments below Columbo correctly points out that things have gradually been changing all the time. My comments above are relative to C++11.
For the draft C++17 (which seems stable at this point) things have changed somewhat:
If alloc_traits::propagate_on_container_move_assignment::value is true, the spec now requires the move assignment of the allocator_type to not throw exceptions (17.6.3.5 [allocator.requirements]/p4). So one no longer needs to check is_nothrow_move_assignable<allocator_type>::value.
alloc_traits::is_always_equal has been added. If this is true, then one can determine at compile time that point 3 above can not throw because resources can be transferred.
So the new noexcept spec for containers could be:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment{} ||
alloc_traits::is_always_equal{});
And, for std::allocator<T>, alloc_traits::propagate_on_container_move_assignment{} and alloc_traits::is_always_equal{} are both true.
Also now in the C++17 draft, both vector and string move assignment carry exactly this noexcept specification. However the other containers carry variations of this noexcept specification.
The safest thing to do if you care about this issue is to test explicit specializations of containers you care about. I've done exactly that for container<T> for VS, libstdc++ and libc++ here:
http://howardhinnant.github.io/container_summary.html
This survey is about a year old, but as far as I know is still valid.
I think the reasoning for this goes like this.
basic_string only works with non-array POD types. As such, their destructors must be trivial. This means that if you do a swap for move-assignment, it doesn't matter as much to you that the original contents of the moved-to string haven't been destroyed yet.
Whereas containers (basic_string is not technically a container by the C++ spec) can contain arbitrary types. Types with destructors, or types that contain objects with destructors. This means that it is more important to the user to maintain control over exactly when an object is destroyed. It specifically states that:
All existing elements of a [the moved-to object] are either move assigned to or destroyed.
So the difference does make sense. You can't make move-assignment noexcept once you start deallocating memory (through the allocator) because that can fail via exception. Thus, once you start requiring that memory is deallocated on move-assign, you give up being able to enforce noexcept.
The move assignment operator in container classes is defined to be noexcept because many containers are designed to implement the strong exception safety guarantee. Containers implement the strong exception safety guarantee because back before there were move assignment operators, the container had to be copied. If anything went wrong with the copy, the new storage was deleted and the container remained unchanged. Now we're stuck with that behavior. If the move assignment op is not noexcept, the slower copy assignment operator is invoked instead.