6 ms·
C++ just gives you the option to have it both ways. You are free to design your library in a way that your users only see one of these.
by fooker 1y ago
C++ just gives you the option to have it both ways.
You are free to design your library in a way that your users only see one of these.
- tialaramex 1y agoWhat developers actually wanted, even years back when C++ didn't have anything named "move" was the destructive move semantic. It's an elegant, easy to understand feature for a language. I like it very much in Rust which has it. C++ didn't get that. The proposal paper at the time says it's impossible (for C++). But what they did propose was the feature you've seen in C++ today, which they call "move", but it has slightly odd (though convenient to implement, Worse Is Better after all) behaviour. Now you can make this C++ "move" behaviour out of destructive move, that behaviour is roughly what Rust calls std::mem::take and it's sometimes useful, which is why that function is provided. But, often it's not really what you wanted, and if you actually wanted destructive move but only have this C++ imposter then you need to perform the entire take, then throw away the newly created object. You will find lots of C++ code doing exactly that. So, no, you can't "design your library" to deliver the desirable property in C++. It's just another of the dozens of nagging pains because of design mistakes C++ won't fix.
- fooker 1y agoI am still confused about what you are claiming to be not possible in C++ when you are designing your library. Yes, I understand that there’s a lot of bad code out there and C++ happily enables that. But that was not my point.
- tialaramex 1y agoMaybe it will be clearer if you read and understand my favourite function from Rust's standard library (its full name is core::mem::drop) pub fn drop<T>(_x: T) {}
- fooker 1y agoGreat. template <typename T> void drop(std::unique_ptr<T> &&) {} Also, you don’t really need this because of RAII. You can make it simpler, and here’s how you’d do this in C++26. template <std::movable T> void drop(T &&) {} It can get even simpler! void drop(std::movable auto){} Do you see my point about C++ incorporating the good ideas at a glacial pace?
- aw1621107 1y ago> template <typename T> void drop(std::unique_ptr<T> &&) {} So what happens for objects that aren't behind a unique_ptr? > Also, you don’t really need this because of RAII. drop() indeed isn't used much because of RAII, but it is handy for those instances where you do actually want to dispose of something "early".
- fooker 1y agoSee the other versions I added. > dispose of something "early". This is a bit of an anti pattern in C++, but doable of course.
- ninkendo 1y agoIt’s not just for disposing things, it’s also used to decompose things into their constituent parts… like if I had something representing an HTTP response and it contains headers and a body, I could write a `fn into_parts(self) -> (Headers, Body)` that returns the parts you care about while destroying the response object. This is useful in the grpc library I use, which has a response type with some metadata about the response, and an “inner” value that represents the thing the grpc method returned. If you just want the inner thing and don’t care about the metadata, there’s a `fn into_inner(self)` that destroys the response and leaves you the inner value. You can write similar methods in C++ but they require leaving the original value in an “empty” state so that you can avoid expensive copying of things while still letting the moved-from value be “safe” to use. But that affects your API design… you now have to make the methods that fetch the “inner” thing be nullable/optional so that you can represent “oh this was moved from so there’s no body any more”. You don’t have to do that in Rust: moves are just a memcpy and the compiler will reject programs that use the moved-from value.
- ninkendo 1y agoWhat’s not possible in C++ is the actual prevention of the moved-from value from being used after the move. Entire API’s are designed around this: my type that tracks a background task can have a shutdown() method that moves self, so that if you call foo.shutdown(), you can’t use foo any more. This is more than just preventing a value from being used, it also facilitates making a rule that “moves are only just memcpy()”, and it can actually be enforced: C++ move semantics require you to write arbitrary code that pillages the moved-from value (like setting the heap pointer of a moved-from value to nullptr) to ensure the old value is safe: rust just says “nope, there’s no constructor, moves are just a memcpy. We will keep it safe by simply not letting code use the old value.” C++ can never have this unless they offered yet another mutually incompatible form of move semantics (lol, what would the sigil be? &*&?)