| ▲ | nixpulvis 3 days ago |
| Being able to accidentally use a value after moving it is tragic. |
|
| ▲ | malkia 3 days ago | parent | next [-] |
| Yup - I wish that was not the case, I've learned relatively recently that the state the variable becomes (after moving) is "valid, but unspecified" - e.g. you can still destroy it (I guess how would RAII work otherwise), you can still assign to it, and even check some basic properties (!) - but you can't know anything about the actual value (contents) it carries (weird) It's like a tombstone for a deleted file, object - something that tells you - "Here lived Obj B Peterson, was nice folk, but moved away to a higher place" |
| |
| ▲ | vitus 3 days ago | parent | next [-] | | > "valid, but unspecified" Annoyingly, it depends on the type, sometimes with unintuitive consequences. Move a unique_ptr? Guaranteed that the moved-from object is now null (fine). Move a std::optional? It remains engaged, but the wrapped object is moved-from (weird). Move a vector? Unspecified. | |
| ▲ | senderista 3 days ago | parent | prev [-] | | Those semantics are entirely on you, the implementer, to enforce. Move semantics is basically a contract between move ctor/assignop impls and dtor impls: the former must somehow signal moved-out status (typically by nulling out a ptr or setting a flag), and the latter must respect it. And of course the client shouldn't use a moved-from object in invalid ways. All of this is completely ad-hoc and unenforceable. | | |
| ▲ | malkia 3 days ago | parent [-] | | Well, it's not on me, because I'm often the user of someones library, framework, etc, and then it's up to whether it was concious or unconcious decision that the type behaved that way, and then you have to mix things to work together and you end up with this really "unspecified" way, hence you put some rules - "Don't do this" - don't use object after std::move, even if it might be allowed. I'm still baffled. | | |
| ▲ | senderista 3 days ago | parent [-] | | Well they couldn't do much better without supporting destructive moves. Rust shows how simple move semantics can be when they're designed into the language. |
|
|
|
|
| ▲ | jandrewrogers 3 days ago | parent | prev [-] |
| It may seem inelegant but it pretty cleanly addresses real edge cases in systems-y code where the object necessarily has a shorter lifetime than its associated memory for strict safety. In these cases, something resembling "use after free" is unavoidable if the move is destructive even though the object is semantically dead. Putting the object in a sentinel state where it is alive but not usable captures the semantics of deferred destruction pretty well. |
| |
| ▲ | nixpulvis 3 days ago | parent [-] | | In practice it just means a lot of sentinel values and unuseful potential accesses. | | |
| ▲ | jandrewrogers 3 days ago | parent [-] | | Not really. In the vast majority of cases this is all elided. There is no cost, either in lines of code or runtime. When you are decoupling memory lifetimes from object lifetimes it is pretty explicit. It isn't like this sneaks into your code on its own. You have to manage the implications of it yourself. | | |
| ▲ | nixpulvis 3 days ago | parent [-] | | It's not about cost of correct code, it's about accidental wrong code. | | |
| ▲ | jandrewrogers 3 days ago | parent [-] | | That’s really not a thing. By default it is correct, you have to go out of your way to make it incorrect. | | |
| ▲ | nixpulvis 2 days ago | parent [-] | | It's actually really easy... std::vector<int> a = {1,2,3}; // Added this without thinking about future uses of a. std::vector<int> b = std::move(a); use_vec(b); // ... use_vec(a); |
|
|
|
|
|