1

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);
alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Can you define the g and f prototypes? – alex Nov 05 '21 at 06:33
  • The reason why I ask you to define the prototypes is because usually the prototypes will tell you if it's okay to resize it or not based on the contract you have with the function you call. If you pass an r value reference and you aren't the concierge of it, then what do you care what happens to it besides memory consumption? – alex Nov 05 '21 at 06:37
  • @alex, for the sake of argument, let's say `template void f(std::vector&&);` and `template void g(std::vector const&);` and `template void h(It first, It last);`. – alfC Nov 05 '21 at 06:54
  • 2
    Your claim about case 0 is incorrect. `std::move(v)` has no direct effect on `v`, so the effect (if any) of `f(std::move(v))` depends on `f()` itself - whether it accepts an argument by value or by reference (`&` or `&&`) and - if by reference - what it does with its argument. Even if it does have an effect, it will leave `v` in a valid but unspecified state (which means, among other things, that `v` can be assigned or destructed safely). Since you think your first claim is uncontroversial, and I dispute it, I haven't read further. – Peter Nov 05 '21 at 07:02
  • 1
    For more information relevant to my previous comment, have a look at https://stackoverflow.com/questions/7027523/what-can-i-do-with-a-moved-from-object – Peter Nov 05 '21 at 07:04
  • `v = h();` seems to be wrong when the return type of `h()` is `void`. You should provide a complete example including definitions of all the functions `f`, `g`, and `h`. The answers very much depend on what they actually do with passed arguments. – Daniel Langr Nov 05 '21 at 07:05
  • @Peter, sorry for the confusion, the paragraph was poorly titled. I agree with you. Assignment after move is uncontroversially ok. I fixed the text now. – alfC Nov 05 '21 at 07:05
  • @DanielLangr, sorry, I used `h` for different things. Fixed now. – alfC Nov 05 '21 at 07:06
  • @DanielLangr, giving defintions for `f`, `g` and `h` will likely miss the generic aspect of the question. I gave the signatures at the end because I think they matter. Assume that these are generic functions that are templated. – alfC Nov 05 '21 at 07:08
  • "_To summarize, is it correct to resize a vector whose elements have been (possibly) moved-from?"_ — Doesn't the question you linked answer this question? What do you miss there? – Daniel Langr Nov 05 '21 at 07:09
  • @alfC I don't think so. For example, you say that this is _very likely problematic_: `f(std::move(v)); g(v);`. I don't see any reason why it should be. Unless `g` expects the original vector elements, which is not clear without its definition. In general, there is nothing problematic here. – Daniel Langr Nov 05 '21 at 07:11
  • I agree. What I mean is that it is a code smell, since it would be strange to a use `v` that is in an undefined state. The discussion is below the code. Even if you think it is ok, the idea was to introduce the last case. I will change the text to "this is likely smelly code". – alfC Nov 05 '21 at 07:17
  • @DanielLangr, no, it is not the same question. My final question is in the case that the elements themselves are moved and not the whole vector. – alfC Nov 05 '21 at 07:23
  • @alfC Your question about Case 3 is whether _it is correct_. Yes, it is. From the perspective of the standard, there is nothing incorrect about it. Whether it "smells"? Maybe. This is a matter of opinion. – Daniel Langr Nov 05 '21 at 07:28
  • A moved-from vector is a vector with unspecified but stable contents which can be assigned or destructed. A vector with moved-from elements is a vector which contains elements in a state which depends on what the move assignment for those elements grants. – Scheff's Cat Nov 05 '21 at 07:57

2 Answers2

5

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
1
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?).

Jarod42
  • 203,559
  • 14
  • 181
  • 302