0

Coming from another question (actually, rather this one, but the former one is better reference), I could not find appropriate reference in the standard, apart from 20.5.5.15:

Objects of types defined in the C++ standard library may be moved from (15.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.

Are there any requirements to the elements previously contained in the destination container, e. g. being destroyed before the assignment?

Example:

std::list<SomeClass> v1({10, 12});
std::list<SomeClass> v2({7});
v1 = std::move(v2);
for(auto sc : v2)
{
    std::cout << sc << ' ';
}

While GCC did not output anything at all (std::vector and std::list alike), would be receiving 10 12 as output legal (appropriate operator<< provided) (e. g. received by just swapping the contents, especially not deleting the objects previously contained)?

As of now, I'd say "yes", but don't feel sure enough to rely on and too curious not to post a question...

If legal, would it come unexpected for any developers if elements are not destroyed immediately (e. g. in consequence some resources still held open while developer expects them to get closed)?

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • 1
    I'm confused as to whether you are asking about the elements that began in the moved-from container, or those that began in the moved-to container – Caleth Oct 08 '18 at 09:14
  • @Caleth Those in the moved-to. Hm, but those in moved-from get interesting, too (might get a separate question): If they *don't* provide move semantics, would still moving them by swapping container contents be legal? – Aconcagua Oct 08 '18 at 09:17
  • In your example, all of "", "10 12", "7" are possible outputs, along with every other sequence of `MyClass` values ( but I don't expect *any* implementation to emit any other sequence ) – Caleth Oct 08 '18 at 09:18
  • @Aconcagua But you don't do anything in your example with moved-to objects. You move from `v2` and check `v2`... Do you ask whether moved could be implemented as swap, and be legal as an example? – luk32 Oct 08 '18 at 09:18
  • @luk32 Exactly... I'm interested in what happens to those elements that were in `v1` *before* the move, especially if it is *legal* just to leave them in `v2` afterwards. – Aconcagua Oct 08 '18 at 09:21
  • @Aconcagua I think the code example would be better phrased if you had a class with a dtor with side-effects and ask whether dtor effects for elements in `v1` are guaranteed. It would be more representative. Currently you are trying to operate on iterators of object in unspecified state which I think is UB, so you enter anything can happen land, including printing `v1` elements. – luk32 Oct 08 '18 at 09:24
  • @luk32 no, iterating the moved-from container is fine, because it is in a valid state. It is merely unspecified what that valid state is. – Caleth Oct 08 '18 at 09:25
  • @Caleth That's what I wasn't sure. Though, the main point still holds. The results of current example are unpredictable, and using dtor with side effect will better help to establish what happens to moved-to objects. Though I am pretty sure that in general case swapping ownership of contained objects cannot happen (by default), as it would lead to all sorts of problems. – luk32 Oct 08 '18 at 09:30
  • @luk32 I don't think just swapping contents would lead to problems other than users forgetting to apply appropriate actions: If we did rely on side effects immediatly, we could explicitly either clear the target before the assignment or the source afterwards. Solely, just swapping would violate the guarantees Caleth pointed to in his answer given in the meanwhile... – Aconcagua Oct 08 '18 at 10:09
  • Usually when I implement move assignment I default to doing a swap between the two containers. The move assignment operator is passed a guaranteed temporary so I know my old contents will be cleaned up. If you wish to break this guarantee then it's the responsibility of the caller to do the right thing(tm). – Richard Critten Oct 08 '18 at 10:49
  • @RichardCritten Where would `std::move(some_global)` produce a guaranteed temporary? If it does, I must have fundamentally misunderstood rvalue references... – Aconcagua Oct 08 '18 at 10:55
  • @Aconcagua `X::operator=(T&& rhs);` is guaranteed to only be called with a temporary (a value about to expire). `std::move` casts `some_global` into a temporary. If you don't want `some_global` to be treated as a temporary by the move assignment operator don't do the cast, you will then call the copy assignment operator instead. – Richard Critten Oct 08 '18 at 11:14
  • @RichardCritten `std::move` casts into an rvalue reference - which has characteristics of a *reference*; try this: `struct C { int n; C(int n) : n(n) {} C(C&& other) : n(other.n) { other.n = 0; } }; C from(7); C to(std::move(from)); std::cout << from.n;` - from and to can be globals as well... – Aconcagua Oct 08 '18 at 11:21
  • @Aconcagua no contradiction there so I don't get the point. When I swap old and new I am assigning to the rhs (via the reference to the temporary). I am just taking advantage of the fact that the rhs is a temporary and it's going to do my clean up of my old contents for me. If you wish to lie to the move assignment operator and pass something which should not be treated as a temporary you can (use `std::move` on the rhs). But the caller then has to deal with the consequences. – Richard Critten Oct 08 '18 at 11:26
  • @RichardCritten Point is: `std::move` returns a reference to something that is only as much a temporary as the argument itself was. There is absolutely no requirement for the argument being a temporary (feel free to refer to the standard to prove me wrong), thus claiming a *guarantee* for temporality is just plain wrong. – Aconcagua Oct 08 '18 at 11:50
  • @RichardCritten On the other hand, if you swap containers in your *custom* classes, that should be absolutely fine, as *you* do not provide the same guarantees as the standard does for *its* container classes... And then I agree, if user moves non-temporaries (but that's no lying), he/she must know how to correctly deal with their contents afterwards (hopefully, your custom classes are documented appropriately...). – Aconcagua Oct 08 '18 at 11:51

1 Answers1

1

In [container.requirements.general], we see

All existing elements of a are either move assigned to or destroyed. Ensures: a shall be equal to the value that rv had before this assignment.

Where a is the destination and rv is an rvalue. This could be achieved by swapping1 with elements of the source destination, but most likely is done by resizing then moving.

  1. It would have to be through a non-specialisable __swap, to ensure the move assignments do occur.
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Hm, assuming a has more elements than rv, then just swapping would only be valid if the elements' destructor doesn't have side effects (but we'd have to adjust v2's size)? If it has and we *did* swap, we'd have to call the destructors for the surplus elements (so in my example above, output `10 12` would not be legal as the second object would have to be destroyed, but `10` would be, provided the moved-from object received this value while being moved. And analogously, we'd have to assure that any side effects of the move-ctor occur as well for those elements not destroyed.Do you agree? – Aconcagua Oct 08 '18 at 09:53
  • By the way, the paragraph is incomplete: what happens to surplus elements in `rv`? I'd assume now they are required to be move constructed in `a`, but the standard is not explicit about. Or is this stated elsewhere? – Aconcagua Oct 08 '18 at 10:16
  • @Aconcagua `rv` is a moved from standard library object. It will be in a "valid but unspecified state". With regard to side effects of the move, all this possible behaviour can be predicated on [`std::is_trivially_move_assignable`](https://en.cppreference.com/w/cpp/types/is_move_assignable) – Caleth Oct 08 '18 at 10:41
  • @Aconcagua `a` has to increase in size if `rv` was bigger to be equal to `rv`'s old value. The new elements are probably move constructed. – Caleth Oct 08 '18 at 10:48
  • Not sure if I should feel insulted... Of course `a` has to be increased. Point is: the standard is not *explicit* about the move construction, so there seems to be a gap. – Aconcagua Oct 08 '18 at 11:03
  • About swapping: So we could do: `if constexpr(is_trivially_move_assignable_v) { destorySurplus(); swap(); } else { ... } `, if I see this right... – Aconcagua Oct 08 '18 at 11:07
  • @Aconcagua yes, it does seem to be a gap. It only talks about move assignment and allocating space for excess elements, but that implies move assigning into unconstructed objects. Either some default construction, or move construction, is occurring – Caleth Oct 08 '18 at 11:16