I am trying to understand the generic rules of move semantics. Specifically of containers and contained elements.
The reason is that I am trying to understand move in the context of ownership and iterator invalidation.
To do that I am going through some cases with increasing complexity involving a typical container, a general contained type T
, general g
and f
functions.
(Maybe an important extra detail is that f
might or might not actually do a move operation, or it could be contingent at runtime.)
The idea is to introduce Case 3 which is the core of this question.
Case 0
First a fairly uncontroversial case, this is ok:
std::vector<T> v(100, t);
f(std::move(v));
v = make_a_vector();
Case 1
However, use-after-move is likely smelly code
std::vector<T> v(100, t);
f(std::move(v));
g(v);
I think most people agree that this above is not ok.
The rule being (as far I know) that the only operation after move should be assignment.
I think this is particularly because it is undocumented (undefined but valid state) what is the state of a moved-from vector, (or even if it was moved at all.).
So, at best v
is empty and at worst v
is an unspecified state and therefore g
could do something unspecified in this scope.
Case 2:
std::vector<T> v(100, t);
f(std::move(v));
v.resize(120);
Is it correct?
It is not an assignment, but resize
has no preconditions. (found this Can I resize a vector that was moved from?)
Case 3:
Now the really tricky case.
std::vector<T> v(100);
h(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
v.resize(120);
(Here, h
is a function that takes iterators, assume that implicitly it refers to the range [iterator1, iterator2)
.)
Is this correct code?
The reason is that .resize
seems to be going to play, move, swap and copy moved-from object of type T
.
To summarize, is it correct to resize a vector whose elements have been (possibly) moved-from?
EDIT: For the sake of argument, let's specify the signatures of the functions in case they are relevant to the answer(s):
template<class T> void f(std::vector<T>&&);
template<class T> void g(std::vector<T> const&);
template<class It> void h(It first, It last);
CodePudding user response:
If it's a vector of standard-library objects, then it is guaranteed to be safe to resize the vector. See What constitutes a valid state for a "moved from" object in C 11? .
If a vector of your own objects -- then the question comes back to whether you designed your own objects to be valid once moved from.
CodePudding user response:
Your question is well stated, but I think it is not something that is directly answered in the standards. As a programmer with experience in c , I will attempt to answer your question. In c , you need to know who is in your house and who is outside your house. If this container is yours, then you own it, and you should resize it. If you are passed a const &&
, then don't resize it. You should own the memory you create, and you MUST
manage it for the rest of the callers. These move semantics are only there to optimize your code, and if you are using them correctly, then you won't have to invoke std::move
because the compiler will handle it for you. In addition, you should use the const
keyword to indicate it's not okay to modify the referenced space.
Let's look at a simple example.
int function(const int && p);
int function(const int && p) {
p = p 1;
return p 1;
}
int main() {
return function(1);
}
This will not compile and fail because you started to modify p
in the function
definition. The prototype tells you not to do that, and you should get a compiler error if you're building c code correctly.
My final answer is Yes, if you move into your space, and NO if you try to access after you have been moved.
CodePudding user response:
std::vector<T> v(100, t);
f(std::move(v));
v.resize(120);
is similar to your case 1
std::vector<T> v(100, t);
f(std::move(v));
g(v);
vector::resize
has no prerequires, but you don't know previous state.
it might be empty, have some size (with unspecified value).
So after v.resize(120)
, you just know that the new size is 120
, but doesn't know its contents (new elements are default constructed, but which are they?).