Comment by arcticbull
7 years ago
Not all of them, to be sure, lambdas and in particular their capture list syntax is solid. By no means is that universal though, for instance, move semantics.
std::move doesn't move anything, it casts an object as a movable reference (equivalent to static_cast<T&&>). The compiler doesn't preclude you from accessing the old object (or say anything at all) and there's no guarantee it actually did get moved, it's just a hint. That's not real move semantics, it's a shoddy knock-off.
You also have to manage a new constructor, new reference type and the opt-in because the default remains not using move semantics. Worse yet, there's varying agreement on what you can or should do with the source of a moved value, the STL containers will all continue to let you use the old value, it's just empty now. That's not standard behavior because there is none, and it's de facto now because it's in the STL. What a nightmare. [1]
std::variant is supposed to be associated values on enums, but of course, it doesn't do that either. You don't match, you create a new struct with a bunch of operator() methods on it [or overload a lambda?!] and throw it at std::visit. You can only have one variant of each type. Then it throws exceptions if you start mucking about in ways you shouldn't. There's no context. It's dreadful. [2]
[1] http://yacoder.guru/blog/2015/03/14/cpp-curiosities-std-move...
I think the way you are thinking about move is all wrong.
In any kind of correct code, the difference between a move and a copy is only performance. If a copy were to happen where a move was requested then the code is just as correct, so I find it strange to get so hung up on it not being “real”.
Also, if move is the only available option, and move cant happen, you get a compiler error. If performance is correctness for a type that is expensive to copy, make copy not an option.
Also, there is a move constructor by default so it’s not opt-in, you opt out only if you start screwing around with copy / assignment / destructors which you usually shouldn’t need in modern code anyway.
Sure, the state of moving on the moved from object is unspecified, but really, I can’t think of a time when I’ve written code that would care. It’s kind of a non-problem.
If you really want to reuse an object after a move I question your motives, but you should just reinitilaise it by assigning a freshly constructed value and the result of that is of course standard.
> In any kind of correct code, the difference between a move and a copy is only performance. If a copy were to happen where a move was requested then the code is just as correct, so I find it strange to get so hung up on it not being “real”.
That's just not true when you take smart pointers into account. unique_ptr is pretty obvious, since it can't be copied. But shared_ptr is more devious, as there is a clear semantic difference between giving someone a copy of your shared_ptr vs moving your copy to them. And, given that destructors are often used for more than simple resource cleanup (e.g. they are sometimes used for releasing locks), the difference between a move and a copy can have a huge impact on program behavior.
Lambda captures make it pretty easy to create nasty memory safety issues via dangling references. Sure, you could create such issues with the equivalent manually written closure-structs, but lambda captures pave the dangerous path.
But cppcheck can find a lot of dangling references with lambda captures.
I would consider your counter examples rather compelling, thanks for the details. I never particularly enjoyed move semantics, so you have a point there.