I know that generally the standard places few requirements on the values which have been moved from:
N3485 17.6.5.15 [lib.types.movedfrom]/1:
Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
I can't find anything about vector
that explicitly excludes it from this paragraph. However, I can't come up with a sane implementation that would result in the vector being not empty.
Is there some standardese that entails this that I'm missing or is this similar to treating basic_string
as a contiguous buffer in C++03?
So for the move constructor, yes, the moved-from vector will always be empty. This is not directly specified, but falls out of the complexity requirement, and the fact that there is no other way to implement it.
Does move changes the capacity of the vector? std::move is just a cast. It does nothing to the object. It onky changes the type of the reference.
C++ Vector empty() empty() returns true if the vector is empty, or false if the vector is not empty.
In this case, all the elements are deleted, but the name of the vector is not deleted. The second way to delete a vector is just to let it go out of scope. Normally, any non-static object declared in a scope dies when it goes out of scope. This means that the object cannot be accessed in a nesting scope (block).
I'm coming to this party late, and offering an additional answer because I do not believe any other answer at this time is completely correct.
Question:
Is a moved-from vector always empty?
Answer:
Usually, but no, not always.
The gory details:
vector
has no standard-defined moved-from state like some types do (e.g. unique_ptr
is specified to be equal to nullptr
after being moved from). However the requirements for vector
are such that there are not too many options.
The answer depends on whether we're talking about vector
's move constructor or move assignment operator. In the latter case, the answer also depends on the vector
's allocator.
vector<T, A>::vector(vector&& v)
This operation must have constant complexity. That means that there are no options but to steal resources from v
to construct *this
, leaving v
in an empty state. This is true no matter what the allocator A
is, nor what the type T
is.
So for the move constructor, yes, the moved-from vector
will always be empty. This is not directly specified, but falls out of the complexity requirement, and the fact that there is no other way to implement it.
vector<T, A>& vector<T, A>::operator=(vector&& v)
This is considerably more complicated. There are 3 major cases:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment
evaluates to true_type
)
In this case the move assignment operator will destruct all elements in *this
, deallocate capacity using the allocator from *this
, move assign the allocators, and then transfer ownership of the memory buffer from v
to *this
. Except for the destruction of elements in *this
, this is an O(1) complexity operation. And typically (e.g. in most but not all std::algorithms), the lhs of a move assignment has empty() == true
prior to the move assignment.
Note: In C++11 the propagate_on_container_move_assignment
for std::allocator
is false_type
, but this has been changed to true_type
for C++1y (y == 4 we hope).
In case One, the moved-from vector
will always be empty.
allocator_traits<A>::propagate_on_container_move_assignment::value == false && get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment
evaluates to false_type
, and the two allocators compare equal)
In this case, the move assignment operator behaves just like case One, with the following exceptions:
T
, and thus so does case Two, even though case Two doesn't actually execute those extra requirements on T
.In case Two, the moved-from vector
will always be empty.
allocator_traits<A>::propagate_on_container_move_assignment::value == false && get_allocator() != v.get_allocator()
(propagate_on_container_move_assignment
evaluates to false_type
, and the two allocators do not compare equal)
In this case the implementation can not move assign the allocators, nor can it transfer any resources from v
to *this
(resources being the memory buffer). In this case, the only way to implement the move assignment operator is to effectively:
typedef move_iterator<iterator> Ip; assign(Ip(v.begin()), Ip(v.end()));
That is, move each individual T
from v
to *this
. The assign
can reuse both capacity
and size
in *this
if available. For example if *this
has the same size
as v
the implementation can move assign each T
from v
to *this
. This requires T
to be MoveAssignable
. Note that MoveAssignable
does not require T
to have a move assignment operator. A copy assignment operator will also suffice. MoveAssignable
just means T
has to be assignable from an rvalue T
.
If the size
of *this
is not sufficient, then new T
will have to be constructed in *this
. This requires T
to be MoveInsertable
. For any sane allocator I can think of, MoveInsertable
boils down to the same thing as MoveConstructible
, which means constructible from an rvalue T
(does not imply the existence of a move constructor for T
).
In case Three, the moved-from vector
will in general not be empty. It could be full of moved-from elements. If the elements don't have a move constructor, this could be equivalent to a copy assignment. However, there is nothing that mandates this. The implementor is free to do some extra work and execute v.clear()
if he so desires, leaving v
empty. I am not aware of any implementation doing so, nor am I aware of any motivation for an implementation to do so. But I don't see anything forbidding it.
David Rodríguez reports that GCC 4.8.1 calls v.clear()
in this case, leaving v
empty. libc++ does not, leaving v
not empty. Both implementations are conforming.
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