3

Consider a class:

class foo {
public:
  foo(foo &&rhs) { /* some code */ }
  ~foo() noexcept { /* code that does destruction of owning objects */ }
private:
  /* some non-trivial pointer graph like structure */
};

Assume:

{
  foo &f = get_from_other_class();
  foo g = std::move(f);
  // some time later f goes our of scope or the owning object is destroyed.
}

What post conditions apply to f after std::move(f) has been executed?

Note

I have the suspicion that f must still be cleanly destructible (not destroying owning content of g) but I haven't found the corresponding quote in the C++11 standard. I'm skimming through 12.8 copying and moving class objects.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76

6 Answers6

3

Every object must be destructible, unless you only ever allocate it dynamically. After having been "moved from", i.e. having its value bound to an rvalue reference (such as the first parameter of a move constructor), an object is still required to be valid and destructible, but typically it can be in an "unspecified" state and you are simply not supposed to look at the object any more and either let it go out of scope or reassign it.

Some types make stronger guarantees, for example std::unique_ptr promised that when move-constructing, it leaves the moved-from object in a state equal to nullptr.

The general idea behind rvalues and rvalue references is that an object that an rvalue should have no aliases, and specifically, when an object is bound to an rvalue reference, then there is no other alias of the object. The standard library makes those assumptions for the rvalue reference interfaces it exposes.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    where is that actually stated? I mainly find references to copy-elision. and default implementations in the standard. – Alexander Oh Oct 26 '14 at 23:42
  • so technically I would be allowed to for instance call a placement new on this object then right? – Alexander Oh Oct 26 '14 at 23:44
  • @Alex: Not quite. The object is still valid and *needs* to be destroyed. If you want to do placement-new, then you first have to call the destructor. But [don't do that](http://stackoverflow.com/q/8829548/596781). Just assign to the object. – Kerrek SB Oct 26 '14 at 23:50
  • 1
    @Alex: [res.on.arguments] – Kerrek SB Oct 26 '14 at 23:52
2

The post-condition is that your moved-from object is in an unspecified, but valid state. And it is precisely that: it is unspecified, meaning that it can be whatever you choose it to be (or the library implementor) as long as it is valid.

Escualo
  • 40,844
  • 23
  • 87
  • 135
2

What post conditions apply to f after std::move(f) has been executed?

This can largely be dictated by the author of foo, but also constrained by the algorithms (std or not) that place requirements on foo.

Many algorithms will require that in its moved-from state foo be Destructible and can be assigned a new value. But this depends on the context in which foo will be used.

If foo is used with a std-library component, then the requirements are dictated by Table 20 -- MoveConstructible requirements:

rv’s state is unspecified [ Note:rv must still meet the requirements of the library compo- nent that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not. — end note ]

For example: Let's say you call std::sort with a vector<foo>. std::sort will require of foo: Swappable, MoveConstructible, Destructible, MoveAssignable, and LessThanComparable.

std::sort places these requirements on foo whether or not foo is in a moved-from state.

Strictly speaking one should not need to have foo be LessThanComparable when in a moved-from state if using with std::sort. It would be pointless for an implementor to compare a moved-from object since its value is unspecified. Nevertheless, the C++11 and C++14 standards currently require LessThanComparable. The result need not be sensible, but it is required to not crash if executed. A future standard might relax this requirement, but who knows.

So in summary, the author of foo can state what operations are allowed on a moved-from foo.

And any algorithm can state what it requires of the types it operates on.

In the intersection where foo meets the requirements of an algorithm, code works. The C++ standard does not consider a moved-from state special.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Where does std::sort require that the type being sorted is Destructible? I can't find it. It's bugging me because std::sort requires that the type be MoveConstructible, but how can std::sort use the move constructor unless it can eventually destroy the object it constructed? – Adam M. Costello Nov 20 '20 at 00:18
  • IMHO, the standard is weak in this area. But historically the understanding is that the entire library spec not only requires `destructible`, but also requires `nothrow_destructible`. The evidence of this (which falls short of a well-specified proof imho) is http://eel.is/c++draft/res.on.functions#2.4 – Howard Hinnant Nov 20 '20 at 01:47
1

Table 20 (§ 17.6.3.1) defines MoveConstructible requirements:

+------------+----------------------------------------------------------------+
| Expression |                         Post-condition                         |
+------------+----------------------------------------------------------------+
| T u = rv;  | u is equivalent to the value of rv before the construction     |
| T(rv)      | T(rv) is equivalent to the value of rv before the construction |
+------------+----------------------------------------------------------------+

rv’s state is unspecified [ Note: rv must still meet the requirements of the library component that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not. — end note ]

The note indicates that per @Kerrek's answer that a library component may have different requirements.

1

The moved from object must be ready for a destructor to run.

Per Stroustrup, C++ Programming Language, 4th Edition 2014

If x is moved from, x will have "some moved-from state"...

"for the most interesting case, containers, that moved-from state is "empty" "...

I tend to consider it akin to a default initialized variable.

The value of builtin-types are unchanged after move.

The default moved-from state is one where the default destructor and the default copy assignment work correctly.

One of the definitions of that is if the destructor runs, any resource formerly managed by the object will no longer be managed by the object; the destructor will leave it alone, or be unaware. I suppose this is implementation / user dependent.

In short, you can do whatever correct thing you want with x and it shall be correct, as with a new variable.

codenheim
  • 20,467
  • 1
  • 59
  • 80
-1

since it is move, thus, after g = std::move(f);, semantic content of f should be invalidated, and g is validated. move only keeps one valid copy of semantic content.
the semantic of "invalidate/validate" depends on the class, for example, if f is a string, it should be set to be length of 0, if f is a thread, it should be set to std::thread(). However, the detailed behavior depends on the move constructor of the class.
surely f is still destructable.

Peixu Zhu
  • 2,111
  • 1
  • 15
  • 13