3 ms·
Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rar
by spacechild1 1y ago
Please give me an example for a class that needs to handle empty state in the destructor only because of move operations. These exist, but IME they are very rare. As soon as you have a default constructor, the destructor needs to handle the case of empty state.
- tialaramex 1y agoThis means C++ is riddled with types that have unrelated "I'm empty" state inside them rather than this being relegated to a separate wrapper type. It's Tony's Billion Dollar Mistake but smeared across an entire ecosystem. The smart pointer std::unique_ptr<T> is an example of this, sometimes people will say it's basically a boxed T, so analogous to Rust's Box<T> but it isn't quite, it's actually equivalent to Option<Box<T>>. And if we don't want to allow None? Too bad, you can't express that in C++ But you're right that C++ people soldier on, there aren't many C++ types where this nonsense unavoidably gets in your face. std::variant's magic valueless_by_exception is such an example and it's not at all uncommon for C++ people to just pretend it can't happen rather than take it square on.
- steveklabnik 1y ago(And that difference leads to an ABI difference that makes it not a zero overhead abstraction in the way that Box is…)
- spacechild1 1y agoGreat point! Chandler Carruth explained this in one of this cppcon talks: https://youtu.be/rHIkrotSwcc?t=1047 https://youtu.be/rHIkrotSwcc?t=1047
- spacechild1 1y ago> This means C++ is riddled with types that have unrelated "I'm empty" state Again, these cases are still rare. Most classes either don't require user-defined move operations, or they have some notion of emptiness or default state. > And if we don't want to allow None? Too bad, you can't express that in C++ That's actually a good example! Nitpick: you can express it in C++, just not without additional logic and some overhead :)
- 7jjjjjjj 1y ago>you can express it in C++, just not without additional logic and some overhead :) How?
- spacechild1 1y agoE.g. with a boolean member or by setting a bit in the pointer value.
- sgsjchs 1y agoA socket.
- spacechild1 1y agoHow so? Doesn't your socket class have a default constructor and a notion of open and closed?
- sgsjchs 1y agoIf the moves were destructive, I'd design it to have the default constructor call `::socket` and destructor call `::close`. And there wouldn't be any kind of "closed" state. Why would I want it?
- spacechild1 1y agoYour socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?
- 7jjjjjjj 1y agoWith destructive moves, you can end an object's lifetime whenever you want.
- spacechild1 1y agoHow would I use such a socket class as a member variable? How do I reopen the socket?
- sgsjchs 1y agoReopen by constructing and assigning a new socket.
- spacechild1 1y ago
- ninkendo 1y agoIt’s not just the destructor you have to worry about, it’s all of the state accessible to callers. If you have any type that represents validated data, say a string wrapper which conveys (say) a valid customer address, how do you empty it out? You could turn it into an empty string, but now that .street() method has to return an optional value, which defeats the purpose of your type representing validated data in the first place. The moved-from value has to be valid after move (all of its invariants need to hold), which means you can’t express invariants unless they can survive a move. It is much better for the language to simply zap the moved-from value out of existence so that you don’t have to deal with any of that.
- spacechild1 1y agoFirst, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). Second, why can't the .street() method simply return an empty string in this case? > The moved-from value has to be valid after move (all of its invariants need to hold) The full quote from the C++ standard is: "Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state" AFAIK, it only makes such requirements for standard library types, but not for user defined types. Please correct me if I'm wrong.
- ninkendo 1y ago> First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). It still requires you to come up with somethkng to do to the old value in the move constructor. What would you do in the ValidatedAddress case? Set a flag in the struct called “moved_from” and use that to throw an exception if it’s ever used? Wouldn’t it be nice if you just didn’t need to worry about it? > Second, why can't the .street() method simply return an empty string in this case? In this example I’m referring to a type that represents a “validated” address, so, one that has already passed checks to make sure the street isn’t empty, etc. (it’s the whole “parse, don’t validate” idea, although I’ve never understood why the word “parse” is used when I would’ve just called it “validate just once”.) It is an extremely useful concept for your type system to represents invariants in your data like this. Having to make every type contain an “empty” case, just to make the language’s move semantics work, pokes an enormous hole through this idea. > AFAIK, it only makes such requirements for standard library types, but not for user defined types It makes the requirement because the compiler is not going to stop anyone from using the moved-from value, so you have to think of something to do in the move constructor. You can pinky-swear to never use the moved-from value in your own code (and linters can help here) but the possibility still exists, so it must be solved for.