104

What is the correct way to reuse a moved container?

std::vector<int> container;
container.push_back(1);
auto container2 = std::move(container);

// ver1: Do nothing
//container2.clear(); // ver2: "Reset"
container = std::vector<int>() // ver3: Reinitialize

container.push_back(2);
assert(container.size() == 1 && container.front() == 2);

From what I've read in the C++0x standard draft; ver3 seems to be the correct way, since an object after move is in a

"Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state."

I have never found any instance where it is "otherwise specified".

Although I find ver3 a bit roundabout and would have much preferred ver1, though vec3 can allow some additional optimization, but on the other hand can easily lead to mistakes.

Is my assumption correct?

Alex Bitek
  • 6,529
  • 5
  • 47
  • 77
ronag
  • 49,529
  • 25
  • 126
  • 221
  • 5
    You could just call `clear`, as it has no preconditions (and thus no reliance on the object's state). – Nicol Bolas Feb 06 '12 at 23:26
  • @Nicol: Let's say there was a `std::vector` implementation which stored a pointer to its size (seems silly, but legal). Moving from that vector might leave the pointer NULL, after which `clear` would fail. `operator=` could also fail. – Ben Voigt Feb 06 '12 at 23:27
  • 10
    @Ben : I think that would violate the "valid" part of "valid but unspecified". – ildjarn Feb 06 '12 at 23:29
  • 1
    @ildjarn: I thought it just meant it is safe to run the destructor. – Ben Voigt Feb 06 '12 at 23:36
  • I guess the question is what is "valid"? – ronag Feb 06 '12 at 23:38
  • @Ben : I think it means _all_ class invariants must be met, but this is certainly a murky area to me still. – ildjarn Feb 06 '12 at 23:38
  • These "invariants" and "preconditions" that are mentioned seem to me rather elusive in the standard document. – ronag Feb 06 '12 at 23:40
  • @ronag : In the standard, invariants are outlined in the container overview and preconditions are listed under each member function (labeled "Requires:"). – ildjarn Feb 06 '12 at 23:42

3 Answers3

116
§17.3.26     valid but unspecified state     [defns.valid]

an object state that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type

[ Example: If an object x of type std::vector<int> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false. — end example ]

Therefore, the object is live. You can perform any operation that does not require a precondition (unless you verify the precondition first).

clear, for example, has no preconditions. And it will return the object to a known state. So just clear it and use it as normal.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 2
    Where in the standard can I read about "preconditions" for e.g. std::vector methods? – ronag Feb 06 '12 at 23:35
  • @ronag : For `std::vector<>`, §23.3.6. – ildjarn Feb 06 '12 at 23:40
  • @ronag: In section 23 the member functions are described. Preconditions are stated as "*Requires:*". – Ben Voigt Feb 06 '12 at 23:41
  • 1
    @ronag: §23.2 contains tables where those are listed. – Grizzly Feb 06 '12 at 23:45
  • Are you sure that `clear` will return the object to a know state? Doesn't it just have to return the object to an `empty` state, whatever that means, and doesn't have to mean a state where push_back is valid? – ronag Feb 06 '12 at 23:56
  • 3
    I found the following which is interesting, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html, they write " containers can be 'emptier than empty'". – ronag Feb 06 '12 at 23:57
  • 4
    @ronag : 1) If the container is in a valid state then calling `clear` is valid. 2) While the container _was_ in an unspecified state, calling `clear` puts the container into a specified state because it has mandated postconditions in the standard (§23.2.3 table 100). `std::vector` has a class invariant that `push_back()` is always valid (as long as `T` is `CopyInsertable`). – ildjarn Feb 07 '12 at 00:00
  • 3
    @ronag: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html was quoting one of the national body comments on the "emptier than empty" quote. The national body comment was incorrect. N3241 did not propose such a state. If an implementation of a std::container does have an "emptier than empty" state resulting from a move-from, then that state must be a valid state (i.e. you can do anything with that object that requires no preconditions). – Howard Hinnant Feb 07 '12 at 03:56
  • Whilst a "valid but unspecified state" is fine from a _correctness_ point-of-view, if you care about _performance_ then it's worth being aware that reusing a moved-from object can have very different performance characteristics for the different standard library implementations. I wrote about one such example with strings [here](http://info.prelert.com/blog/move-copy-and-swap-for-stdstring). In some cases if you intend to reuse a moved-from object you would be better off swapping rather than moving. – dmr195 Jan 21 '16 at 09:50
  • On cppreference.org there's the following statement about `std::vector`'s move c-tor: "After the move, `other` is guaranteed to be empty().". Do you know where does that come from? stdlib part of the standard doesn't mention this. – joe_chip Apr 16 '19 at 22:24
21

The object being in a valid, but unspecified state basically means that while the exact state of the object is not guaranteed, it is valid and as such member functions (or non member functions) are guaranteed to work as long as they don't rely on the object having a certain state.

The clear() member function has no preconditions on the state of the object (other than that it is valid, of course) and can therefore be called on moved-from objects. On the other hand for example front() depends on the container being not empty, and can therefore not be called, since it is not guaranteed to be non empty.

Therefore both ver2 and ver3 should both be fine.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • A vector will always be empty, but that is not true of the general case, (IE array) – Mooing Duck Feb 06 '12 at 23:35
  • "A vector will always be empty", what do you base that on? – ronag Feb 06 '12 at 23:36
  • 1
    @ronag: I meant ver2 and ver3 of course (as should be clear from the text, fixed that typo – Grizzly Feb 06 '12 at 23:39
  • Interestingly, preconditions for `front()` are stated only for `std::array`, and even there not in the table. – Ben Voigt Feb 06 '12 at 23:49
  • 1
    @Ben : §23.2.3 table 100 says that the operational semantics of `front()` are `*a.begin()`, §23.2.1/6 says "*If the container is empty, then `begin() == end()`*", and §24.2.1/5 says "*The library never assumes that past-the-end values are dereferenceable.*". Consequently I think the preconditions for `front()` can be inferred, though it could certainly be made more clear. – ildjarn Feb 07 '12 at 00:10
-10

I don't think you can do ANYTHING with a moved-from object (except destroy it).

Can't you use swap instead, to get all the advantages of moving but leave the container in a known state?

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • +1. swap is a good idea, though it won't work in all cases, e.g. using auto will not work. Maybe a safe_move, which uses swap internally could be an idea? – ronag Feb 06 '12 at 23:27
  • 6
    It's a live object, and you can use any functions that don't have preconditions (aside from invariants) – Mooing Duck Feb 06 '12 at 23:36
  • The primary template for `std::swap` has 2 move assignments, with the targets of those assignments being moved-from values. That counts as "doing something to a moved-from object" to me – Caleth Oct 08 '18 at 11:23