5 ms·
Why isn't a moved-from object always considered "consumed"?
by haberman 7y ago
Why isn't a moved-from object always considered "consumed"?
- jdsully 7y agoBecause its still a legal and valid object according to the standard. A moved from vector might get reused to store new things for example.
- ndesaulniers 7y agoAre you sure? I thought a moved from object was left in an indeterminate state and further use was undefined behavior. Move constructors and move assignment operators that zero out the rhs members try to prevent duplicate references from an object likely to be destructed soon, which might lead to dangling pointers and then use after free in lhs. Can someone cite the spec and prove me wrong, please?
- jdsully 7y agoA move constructor is supposed to leave the donor object in a valid state. All STL objects with one will do so. If you write a poor move constructor that doesn’t do this you will get exactly what you asked for. However the compiler has no way to know you half assed it and must support a legal valid case. See this stack overflow: https://stackoverflow.com/questions/9168823/reusing-a-moved-container https://stackoverflow.com/questions/9168823/reusing-a-moved-... To quote office space, “Your moved from object only needs to be destructible. But some people choose to allow more and we encourage that.”
- comex 7y ago"if (x = y)" is also a legal valid use case (it sets x to y, and then tests whether it's nonzero), but that doesn't stop compilers from complaining if you write it, on the grounds that it's most likely a mistake. They should do the same with use-after-move. There could then be some workaround or annotation to disable the warning if you really did want a use-after-move, as there is for the warning I mentioned (adding extra parentheses).
- 0xffff2 7y agoNote quite the standard, but [1] says under the "Notes" section: >Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state. That is, only the functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from [1] https://en.cppreference.com/w/cpp/utility/move https://en.cppreference.com/w/cpp/utility/move
- connicpu 7y agoThe standard says you must leave the object in a valid state for the destructor to be called, but many STL objects provide stronger guarantees for the state of objects after a move operation. For example, std::vector<>'s move constructor guarantees that the moved-from vector will be `empty()`[1] [1]: https://en.cppreference.com/w/cpp/container/vector/vector https://en.cppreference.com/w/cpp/container/vector/vector
- 0815test 7y ago> The standard says you must leave the object in a valid state for the destructor to be called Note that Rust also uses a bit of a hack - a remnant of the typestate machinery, actually - to deal with cases where a drop implementation (equivalent to a non-default destructor) must be called on something that may or may not have been moved from, in a way that cannot be determined at compile-time. They limit it to that case though, it's a purely-internal detail and does not affect the reference-level ("standard") description of the language.
- kllrnohj 7y agostd::move itself doesn't define what state the object ends up in. The object's type defines that. So it is both undefined (by std::move) and well-defined (by the concrete type). The standard simply requires a moved-from object to still have a valid destructor, but types are free to (and do!) allow for more than that to still be valid. The default move constructor does not zero out the rhs. It simply does a std::move on each of the fields, which for all the primitive types is simply a copy, including for bare pointers. For example this: struct Foo { int a; void* b; }; int main() { Foo f{ 20, (void*) 0xBADBEEF }; Foo f2 = std::move(f); printf("f[%d, %p], f2[%d, %p]\n", f.a, f.b, f2.a, f2.b); return 0; } will print this: f[20, 0xbadbeef], f2[20, 0xbadbeef] There is no active zero-ing or "empty" state at any point unless the specific class defines that its move does that. unique_ptr defines that get() will be nullptr after-move, for example, but that's something unique_ptr itself is specifically defining.
- hedora 7y agostd::move is lower level than that. It is a primitive that you can use to implement whatever lifecycle semantics you want. To see what I mean, in the examples in the article, the author has to tell the compiler all sorts of things that are hard to infer. For example, the object comes into being in the “unconsumed” state (what if I instantiate it with a nullptr? Can I even do that?), and a prerequisite to dereferencing it is that it is “unconsumed”. It is plausible that there would someday be a set(foo&&) method that had “unconsumed, consumed, unknown” as a prerequisite, and always brought the type into the “unconsumed” state. In practice, you shouldn’t be spraying these annotations all over a code base. They should be in library methods that are reused frequently, so that you get a lot of static analysis benefit from a small number of manual annotations.
- ori_b 7y agoBecause move is just a cast to an rvalue reference. It doesn't do anything. It just gives type level hooks for operator overloading.
- mannykannot 7y agoSemantically, it should be, but, on account of the way C++ does things, at some point it is very likely to be destroyed, and whenever that happens, you want it to be in a state such that the running of its destructor has no consequences for the rest of the program.
- deleted 7y ago[deleted]