7

I've got following code:

std::list some_data;
...
std::list new_data = std::move(some_data);
some_data.clear();
...

The question is whether some_data.clear() is necessary? (for the record, some_data will be reused in the future)

ji chengde
  • 81
  • 4
  • 1
    If the list is going to be reused then it would make since to clear it. – RealPawPaw Oct 08 '18 at 07:14
  • 1
    You cannot reuse `some_data` in the future. https://en.cppreference.com/w/cpp/language/move_assignment – pptaszni Oct 08 '18 at 07:15
  • 2
    `some_data` will be left in an undefined but correct state. More on that in this near-duplicate: [Reusing a moved container?](https://stackoverflow.com/questions/9168823/reusing-a-moved-container) – user4581301 Oct 08 '18 at 07:16
  • 1
    Using clear is certainly the safe thing to do. However, there are requirements on what happens to the elements in case of move that effectively mean that the "valid but unspecified state" must be an empty list. – MikeMB Oct 08 '18 at 07:27
  • @MikeMB Which requirements? If you mean the _constant complexity_ of move construction, then it might be possible, e.g., to get a move-from list into a state with some single "dummy" element. Of course, why would anyone do that, but, technically, it is allowed. – Daniel Langr Oct 08 '18 at 07:30
  • @MikeMB no it doesn't follow. – n. m. could be an AI Oct 08 '18 at 07:30
  • 1
    I'd be surprised by an implementation that didn't leave the list empty, but it's not required. Further, if a good compiler saw advantage in not leaving an empty list, it would take it. – user4581301 Oct 08 '18 at 07:35
  • @user4581301 Is the destination forced to destruct already contained objects ? Otherwise, contents could just be swapped, leaving the source non-empty (well, rather in the move assignment case, of course, as on move construction, destination is empty anyway...). – Aconcagua Oct 08 '18 at 07:47
  • @DanielLangr: My mistake - I thought the move constructor was required to directly transfer the elments to the new list (no copy/move of individual elements allowed), but I can't even find the specification of `list(list&&)` in the c++17 standard, so I'm not sure, where I got that from. – MikeMB Oct 08 '18 at 08:02
  • OP, you're supposed to accept the right answer on StackOverflow – Denys Séguret Dec 19 '19 at 07:35

5 Answers5

9

Yes, it's necessary.

Only the std smart pointers are guaranteed to be in a default constructed state after being moved from.

Containers are in an valid, but unspecified state. This means you can only call member functions without preconditions, e.g. clear, that put the object in a fully known state.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • That means move constructors are not properly defined in STL? When we write move constructor we know that it must be moved to default condition right? How STL move constructors cannot guarantee the default state? – Gilson PJ Oct 08 '18 at 07:19
  • 2
    @GilsonPJ Of course move constructors are properly defined. They are defined such that 1) the constructed object will have the same contents as the original object, and 2) the moved-from object will be in a valid bud unspecified state. – Daniel Langr Oct 08 '18 at 07:21
  • 7
    @GilsonPJ it's not just move constructors that be doing moving. Plus the move constructor isn't really interested in the fate of the moved-from object and building in extra brains for them to do so would violate the principle of "You Don't Pay For What You Don't Use". If you want to reuse the object, you pay. You don't make everybody pay. – user4581301 Oct 08 '18 at 07:26
  • 2
    @GilsonPJ _"When we write move constructor we know that it must be moved to default condition right?"_ This is wrong presumption. Nobody forces anyone to get the moved-from object into the _default-constructed_ state. – Daniel Langr Oct 08 '18 at 07:26
  • @DanielLangr: IIRC, The move constructor for most requires that no new objects are created. The only way to advertise this is if the original list is empty after the move. – MikeMB Oct 08 '18 at 07:32
  • 1
    @MikeMB Where is this written, can you quote the Standard? I am interested in this topic. – Daniel Langr Oct 08 '18 at 07:34
  • 1
    @GilsonPJ Also note that, generally (not focusing just on STL types), this quote from cppreference.com holds for move constructors: _"A constructor is called a 'move constructor' when it takes an rvalue reference as a parameter. **It is not obligated to move anything**, the class is not required to have a resource to be moved and a 'move constructor' may not be able to move a resource as in the allowable (but maybe not sensible) case where the parameter is a const rvalue reference (const T&&)."_ – Daniel Langr Oct 08 '18 at 07:38
  • 4
    The only condition for moved-from objects mentioned in the standard is that moved-from objects are in a valid state. This is true for all std types, but the standard can only suggest you do the same for your types. A C++ program isn't invalid if a move constructor leaves an object in an invalid state. It's just bad C++ and every seasoned C++ developer will choke on this type of thing. – rubenvb Oct 08 '18 at 07:39
  • @MikeMB Does moving enforce the destruction of objects contained in the *destination* container? If not, moving could be implemented as just swapping the contents of source and destination - leaving appropriate destruction to the source container (sure, on construction, we'd swap empty anyway, interesting case is move assignment here...). – Aconcagua Oct 08 '18 at 07:41
  • @Aconcagua: The destination list is a newly created list – MikeMB Oct 08 '18 at 07:49
  • @MikeMB See parenthesis in my comments - I was generalising to move assignment as well. Apart from, not being empty after moving would be valid according the standard. Not be meaningful, perhaps, but valid. – Aconcagua Oct 08 '18 at 07:58
  • @Aconcagua: I thought the standard required the list move constructor to directly pass on the individual nodes in the list (no copy or move of the elements allowed), but I can't find a reference to it in the c++17 standard, so I might be wrong. – MikeMB Oct 08 '18 at 08:04
  • @MikeMB [container.requirements.general]/4 tells us the operation must be constant-time, which is effectively the same thing. The standard likes to give requirements in terms of algorithmic complexity, which makes some sense, and theoretically gives implementations latitude in how they actually go about _doing_ stuff, though in practice we can often (like now) make strong deductions about said implementation as a result. Like, a list is always going to have nodes in a linked style because how else would you do it? So pointers to the head node will be swapped because how else would you do it? – Lightness Races in Orbit Oct 08 '18 at 11:20
  • @MikeMB Interestingly, notice that there is an exception for std::array which is permitted to be linear-time in the move constructor, because _other_ constraints effectively mandate no dynamic allocation, and without dynamic allocation you effectively can't just swap/move anything at all. Abstraction leak alert! ;) – Lightness Races in Orbit Oct 08 '18 at 11:22
  • @MikeMB In fact it's not even stated that that rule applies to the move constructor as the rules are given in terms of expressions/statements - but it's clear from the description of this particular rule that the move constructor is being talked about, because it's the list move constructor that would be invoked by such a statement. – Lightness Races in Orbit Oct 08 '18 at 11:23
  • @LightnessRacesinOrbit: Thanks for digging that up – MikeMB Oct 08 '18 at 12:03
  • @LightnessRacesinOrbit Aggregates, where abstraction leaks are the intention. – rubenvb Oct 08 '18 at 13:44
  • @rubenvb C++, where abstraction leaks are deemed a design challenge accepted and successfully completed (for some reason) :P – Lightness Races in Orbit Oct 08 '18 at 13:53
  • A reference counted `std::list` that almost never used the reference count, except when move-constructing a new list, and does careful copy-on-write of contents, with iterators that are aware of the copy-on-write and magically remain valid, would probably be technically legal. Maybe not due to the "reference to elements are not invalidated" rules? A lot of work just to have a moved-from std::list be non-empty tho. – Yakk - Adam Nevraumont Oct 09 '18 at 15:57
5

The working draft of standard (N4713) states about the state of objects after they are moved from:

20.5.5.15 Moved-from state of library types [lib.types.movedfrom]
1 Objects of types defined in the C++ standard library may be moved from. 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.

20.3.25 [defns.valid]
valid but unspecified state value of an object that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type

The only operations you can safely perform on a moved-from container are those that do not require a precondition. clear has no preconditions. And it will return the object to a known state from which it can be used again.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • Have you considered if anything but an empty list satisfies the "valid but unspecified state" requirement in this particular case? – MikeMB Oct 08 '18 at 07:29
  • 1
    @MikeMB A list/vector with N elements where a call to `size()` returns N, but N is otherwise an unspecified number is a perfectly valid but unspecified state. For vector, the capacity also needs to be >= N for that class invariant to be met. – rubenvb Oct 08 '18 at 07:50
  • @rubenvb: It might be valid, but would imply that the move constructor would create new, value_type objects in the source list/vector which would be pretty pointless. – MikeMB Oct 08 '18 at 12:02
2

No it is not necessary to clear the list. It is just reasonable and convenient to do so.

The list will have N elements, each taking an unspecified value, for some N≥0. So if you want to re-poppulate the list, you may in theory assign to the elements that are kept rather than clear and re-insert everything from scratch.

Of course chances that N≠0 are vanishingly small, so in practice clearing is the right choice.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

Short answer:
Yes, you should clear it, because the standard doesn't explicitly define in what state the source list is after the move.
Furthermore, always calling clear() when a container gets reused after being moved from is a simple rule that should not cause any significant overhead even when it is redundant.

Longer answer:
As I mentioned in some comments, I can't imagine any legal and sensible implementation for which the source std::list will be anything but an empty std::list after being used as the source for a move constructor (thanks @Lightness Races in Orbit for digging through the standard). Move constructing must happen in linear time, which doesn't allow any per-object operations and AFAIK it is also not allowed to have a small, fixed-size inplace buffer that could be a source of left-over "zombie" elements in the source list.

However, even if you can track down all of that in the standard, you'd still have to document that everytime you omit the clear() in the code and be wary of someone refactoring the code by replacing the std::list with a homegrown container, that doesn't quite full fill the requirements in the standard (e.g. to make use of the small buffer optimization). Also, the previous statement is only valid for move construction. On moveassignment it is absolutely possible that the moved from container will not be empty.

In summary: Even if it was technically correct to not use clear in this particular case, it is imho not worth the mental effort to do it (and if you are in an environment where it is worth it, you are probably tuning your code to a particular version of the standard library and not the standard document anyway).

MikeMB
  • 20,029
  • 9
  • 57
  • 102
0

It rather depends on what you mean by "reused". There are many reuses that totally define the state of the object, with no regard to it's previous state.

The obvious one is assigning to a moved-from object, which occurs in the naive swap

template <typename T>
void swap(T& lhs, T& rhs)
{
    T temp = std::move(lhs);
    lhs = std::move(rhs); // lhs is moved-from, but we don't care.
    rhs = std::move(temp); // rhs is moved-from, but we don't care.
}

If you instead wish to "reuse" by calling some_data.push_back, then yes, you should clear it first

Caleth
  • 52,200
  • 2
  • 44
  • 75