There is literally no correct way to handle move in an RAII context that doesn’t either (a) behave unexpectedly if you try to use the moved-from object or (b) permit a null value of the object. This isn’t a library problem — it’s a language problem.
What if the moved-from DB object lazily opens a new connection, if someone uses it again? Maybe that’s a null object, but at least the nullness isn’t really observable to the API user. Even the extra latency or possibility of failing to connect must be expected at any time from query() etc. so it changes little.
Also, I would say nothing is “unexpected” behavior if you document it and implement accordingly. And at least for this DB case, handling it is not onerous or stretching the idea of class invariants beyond usability.
I’m probably like what GP said, “high on the feeling of having finally grokked C++, which is no small feat.” But I either want to understand better why move is broken, or we can agree that things like move require too much skill to get right and there are better alternative languages.
> What if the moved-from DB object lazily opens a new connection, if someone uses it again?
Great, so now the stateful settings on my database connection change depending on whether I move from it.
Database connections are kind of a bad example — having a connection drop is not really unexpected behavior, and a program that uses a database should be prepared for a connection to drop, so there’s kind of an invalid state on a connection anyway. But things like file handles or mutex guards aren’t like this — it’s reasonable to expect that, on a functioning system, a file handle won’t go away. And if I’m using a type-safe language that supports RAII, I would like the compiler to ensure that I can’t use an object that isn’t in a valid state.
Rust can do this, as can lots of languages that support “affine” types (that name is absurd). GC languages can kind of do this too, as long as cloning the reference is considered valid. Languages with “linear” types can even statically guarantee that I don’t forget to close my object.
C++ can ensure that an object is valid if it’s in scope, but only if that object is not movable, so “consume” operations are not possible in a type-safe way.