4 ms·
Being able to accidentally use a value after moving it is tragic.
by nixpulvis 11mo ago
Being able to accidentally use a value after moving it is tragic.
- malkia 11mo agoYup - 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 11mo ago> "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 11mo agoThose 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 11mo agoWell, 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 11mo agoWell 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 11mo agoIt 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 11mo agoIn practice it just means a lot of sentinel values and unuseful potential accesses.
- jandrewrogers 11mo agoNot 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 11mo agoIt's not about cost of correct code, it's about accidental wrong code.
- jandrewrogers 11mo agoThat’s really not a thing. By default it is correct, you have to go out of your way to make it incorrect.
- nixpulvis 11mo agoIt'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);