> I will say that programmers very often have bad instincts for when that's a bad idea
True that. memcpy is basically the literal fastest thing your processor can do, it’s trivially pipelined and can be done asynchronously.
If the alternative is heap storage you’re almost always cooked: that heap space is far less likely to be in L1 cache, allocating it takes time and requires walking a free list, dealing with memory fragmentation, freeing it when dropped, etc.
It’s not a bad short-hand to think of the heap as being 10-100x slower than the stack.
It’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.
First, 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.
> 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.
> 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.
Nobody says that the invariants must hold after the object has been moved-from! The only thing you need to do is make sure that the destructor can run and do the right thing.
> 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.
Letting the program crash would be a valid solution (for your own types).
For me the issue with C++ move semantics is not so much that you have to add special logic to your classes, but the fact that moved-from objects can be accessed in the first place. In this respect I definitely agree that destructive moves are better.
When does my wallet slide slightly from the magnetic center and then back into place most often? When I’m getting it out of my pocket.
When am I trying to just use my goddamn phone the most? When I get it out of my pocket.
So, it ends up being that ~50% of the time I need to use my phone, I have to wait for that goddamn 3 second animation first.
If some engineer introduced a 3 second regression in the time for Face ID to unlock your phone 50% of the time, it would be noticed and fixed immediately. But call that 3 second regression a “surprise and delight animation” and suddenly Apple designers love it and force it on you.
Yeah, the problem is Apple and all the other contemporary tech companies have engineers bounce around between them all the time, and they take their habits with them.
At some point there becomes a critical mass of xooglers in an org, and when a new use case happens no one bothers to ask “how is serialization typically done in Apple frameworks”, they just go with what they know. And then you get protobuf serialization inside a plist. (A plist being the vanilla “normal” serialization format at Apple. Protobuf inside a plist is a sign that somebody was shoehorning what they’re comfortable with into the code.)
Y’know the script approach sounds like a good idea.
I also have an OpenBSD box similar to what you describe, but I run ISC dhcpd and BIND because it’s the only setup that does old-school dynamic DNS where the dhcp server sends zone updates to BIND when a lease happens.
But I hate BIND, and not to mention this setup doesn’t work with DHCPv6 (no idea why, it should in principal…) maybe I should just do the “script to read the leases and generate the zone file” approach instead.
The world has been waiting for a DHCP and content DNS server that simply share a common database back-end, meaning no notifications/updates/scripts, for decades. See https://news.ycombinator.com/item?id=44395279 for more.
Sure, after you dismiss the pop-up telling you to become an ars subscriber.
I’m only angry about this because I’ve been on ars since 2002, as a paid subscriber for most of that time, but I cancelled last year due to how much enshittification has begun to creep in. These popups remove any doubt about the decision at least.
(I cancelled because I bought a product they gave a positive review for, only to find out they had flat-out lied about its features, and it was painfully obvious in retrospect that the company paid Ars for a positive review. Or they’re so bad at their jobs they let clearly wrong information into their review… I’m not sure which is worse.)
> It really feels like Apple's teams are not using any of SwiftPM themselves.
They probably never will, at least not for anything that ships with the OS. For Apple, binary size is very important, and it’s essential that they have only one of every library (“project”) installed on the device, and that they use dynamic linking everywhere.
SwiftPM uses static linking to the core, and if Apple were to use it, binary sizes would balloon (not to mention there would be potentially mutually incompatible versions of things.) You could fix this by ignoring version specs in the package file and building just one of every project/framework, and changing it to use dynamic linking everywhere… but at some point you’re just contorting swiftpm to become a big mono-build system, which only Apple would really be using as such.
#define private public
#import <iostream> // muahaha
Or any such nonsense. Nothing I define with the preprocessor before importing something should effect how that something is interpreted, which means not just #define’s, but import ordering too. (Importing a before b should be the same as importing b before a.) Probably tons of other minutiae, but “not leaking context into the import” is a pretty succinct way of putting it.
yeah that, include is a textual replacement, so anything placed before the include is seen by all the code in the include. Not just other preprocessor stuff and pragmas but all of the other function definitions as well. There are some cases where this has legitimate use, but also is one of the main reasons why compilers can't just "compile the .h files separately and reuse that work whenever its included, automatically"
you define #import as "include but no context leaks into it" and that should on its own be enough to let the compiler just, compile that file once and reuse it wherever else its imported. That's like 95% of the benefit of what modules offered but much much simpler
True that. memcpy is basically the literal fastest thing your processor can do, it’s trivially pipelined and can be done asynchronously.
If the alternative is heap storage you’re almost always cooked: that heap space is far less likely to be in L1 cache, allocating it takes time and requires walking a free list, dealing with memory fragmentation, freeing it when dropped, etc.
It’s not a bad short-hand to think of the heap as being 10-100x slower than the stack.
reply