That big ugly object that would get copied on function return -- just create it before the function call and pass it in as a reference! No copy required.
That big ugly object that would get copied on function return -- just create it before the function call and pass it in as a reference! No copy required.
For example, how do you cleanly create a factory function? A pretty simple thing. You'd return a pointer to a dynamically allocated object? But how do you guarantee that the caller doesn't just discard that return value or doesn't forget to delete it? Also this forces dynamic allocation for that object and adds an indirection to access, even when the caller might not want that.
Move semantics allow you to either make your resource wrapping objects movable (so you return them "by value" as value semantics objects but they get moved) or to use something like std::unique_ptr to wrap the returned dynamically allocated object. The former has the advantage that it gives the caller complete flexibility where to store the object (ex. it can store it locally on the stack or as a member) and avoids a pointer indirection.
Similar issues exist for producing copyable but expensive to copy objects (ex. containers). Move semantics allows for a typesafe/clean way to return them from factory functions while not having to worry about lifetime and performance.
Believe me, I would dearly like to learn something new that can benefit me in my daily work. But unfortunately I can't make sense of your examples as they stand so it would be great if you could provide some example code to illustrate your point.
I do not know about your background or experience in C++ and you may very well have valid points (hence my request for code examples). However I get to read a lot of code of inexperienced programmers in my job who use && references and std::move "because that's how you do it in a modern way" and then run into crap like use after move and then wonder what the hell went wrong...
Most newer languages have realized that implicit copy semantics is usually a bad thing, and duplicating objects requires explicitly saying that you're duplicating them (such as calling a .clone() method). Of course, some types are value types, where copy is cheap and better than reference, but such mechanisms are opt-in. C++ automatically generates these mechanisms, and gives you nasty error messages when you opt-out of default copy.
Move semantics are almost always better, but in C++, with its historical baggage of opt-out-of-implicit-copy-semantics, it means that constructing move-only types requires a lot of excessive calls to std::move. Compilers do a good job of telling you when you put one too many calls to std::move in, but the code is definitely verbose compared to C++, to the point that it tends to strongly weigh against actually using C++'s ability to annotate move-only methods. Furthermore, without something like the mechanism in the blogpost here, compilers don't give any indication of API misuse, so you can't leverage move-only types to construct safe-by-construction APIs.
This is something I've been tripping over a lot recently, as I have a type system where calling most methods makes the original object unusable.
This is not true. The rules are simple: rule of three, rule of five, or the recommended one, rule of zero [1]. If you define a copy constructor, the compiler won't generate a move constructor. If you define a move constructor explicitly, the compiler won't generate a copy constructor silently. You have total control over how the language behaves.
So in summary, either stick to the rule of 5 or the rule of zero and you won't be surprised. If you don't mind expensive copies, rule of 3 is sufficient.
[1] https://en.cppreference.com/w/cpp/language/rule_of_three
It generally takes extra effort to force the compiler to use a reference or the move constructor instead of the copy constructor. For example, "for (auto x : container)" will usually involve a copy constructor where a reference might have sufficed. And because copy constructors are generated (unless you take extra effort), there can often be no indication that the most natural form of the construction is actually less efficient than originally desired.
Yes, if you're aware of the gotchas, you can avoid them, but it is consistently extra effort that has to be put into doing so, and slipping up and missing the efforts in a few key places means you won't even be alerted that you might have forgotten something.
With regard to your example, that's why you need to use universal references like "for (auto&& x : container)" [1]. It's just a new way of doing things, but it's hardly ambiguous.
[1] https://isocpp.org/blog/2012/11/universal-references-in-c11-...
I don't mean to sound arrogant but if someone is writing C++ at a level where he/she does accidental copies then that's a very clear sign to me to stay the hell away from move semantics and other advanced features.
I personally find things being copied by default a nice feature. It's more consistent than "PODs by value, objects by reference" such as in Java.
Well; https://groups.google.com/a/chromium.org/forum/#!msg/chromiu...
If move semantics were the default, we'd either get more use-after-frees or require the awkwardness of the borrow checker to have a semblance of sanity.
So? As far as I know this could be 1 year+ 20 repeats.
In contrast to your experience, I missed move semantics since the early 2000s. It’s not just to avoid copies.
How about the ability to avoid pointers, aliasing, and allocation. Move semantics afford more than just avoiding a copy.
That works thanks entirely to move semantics. Do you dispute the usefulness of such a container?
> That big ugly object that would get copied on function return -- just create it before the function call and pass it in as a reference! No copy required.
Or just return it without a std::move and it still won't be copied. In fact 'return std::move(any_local_value)' is not just silly it's bordering on wrong as it defeats some RVO optimizations (and returning a T&& of a local is actually wrong as it's returning a reference to a local). That's not what the purpose or use is of move semantics at all.
> Do you dispute the usefulness of such a container?
Sort of. I agree it can be useful in some cases but in reality the added value over vector<shared_ptr<SomeThing>> or just vector<SomeThingPtr> (both of which do not require move semantics) is way too small IMO to justify the butchering of the language with std::move and rvalue references.
At the end of the day to me std::move is like const_cast. It's a quick fix that you can use to sort out a situation, but you shouldn't be proud of it because if you require it, chances are your design is messed up in a more fundamental way and the actual fix is sorting that out.
Also uses move semantics. It's not _required_ here, no, but it highly benefits from it. Huge efficiency gain to avoid all the atomic refs & unrefs during resize.
> vector<SomeThingPtr>
This is error-prone and a leak risk. You just made the code buggy and worse, kinda like using const_cast.
> At the end of the day to me std::move is like const_cast. It's a quick fix that you can use to sort out a situation, but you shouldn't be proud of it because if you require it, chances are your design is messed up in a more fundamental way and the actual fix is sorting that out.
No it's pretty much the exact opposite. Avoiding std::move or insisting you don't need it is like scattering const_cast everywhere. You're avoiding a thing that helps you categorically avoid bugs (like unique_ptr does) in favor of just winging it and hoping for the best.
Well, that entirely depends on your skill and your overall design. But again, there's still vector<shared_ptr<SomeThing>> to the rescue if we need it.
> Avoiding std::move or insisting you don't need it is like scattering const_cast everywhere.
Strawman. I never said that. I said if you require std::move in a certain situation, have another look at your overall design. Again, same as const_cast.
If your argument against std::move & unique_ptr is "I don't make mistakes" then your ego is vastly too large to be a C++ developer.
The design doesn't change. unique_ptr just solidifies the design such that the compiler can help enforce the design.
> But again, there's still vector<shared_ptr<SomeThing>> to the rescue if we need it.
If you want to insist all heap allocates are always shared_ptr you can certainly do that. Personally I'd consider that dreadfully slow & expensive, and at that point you're far, far better off just using a GC'd language instead.
> I said if you require std::move in a certain situation, have another look at your overall design.
std::unique_ptr single handily proves this wrong.
Please stop the strawmans and polemics and allow your ego to consider the opinion of someone who is, in all likelihood, more experienced in shipping product written in C++.
> If you want to insist all heap allocates are always shared_ptr you can certainly do that. Personally I'd consider that dreadfully slow & expensive
Oh well. I have a feeling we're not going to agree. Maybe let's discuss again in a few years. Maybe I'll have tried std::move then and found that it is somewhat useful in certain special situations.
And maybe you'll have realized that the "dreadfully slow & expensive" shared_ptr doesn't make a difference in terms of end product quality in, I'd wager to say, 99.99% of situations.
> at that point you're far, far better off just using a GC'd language instead.
Uuhhm, no.
For example, if you have a class C containing a pointer P to some data, where C’s destructor frees that pointer:
- a shallow copy of object O would return an object O2, containing that same pointer P, and leave O unmodified.
- a move of O to O2 would (1) make O2 contain P, but also would update O to no longer have that pointer P (it has to make that change, as destructing O at a later time shouldn’t free P anymore)
(1) yes, an implementation could also copy the data or increase a reference count, or, possibly, a zillion different things without running into problems.
Widget::Widget(Widget&& w) {
this->ptr = w.ptr; // shallow copy
w.ptr = nullptr; // nullify the source
}auto* p2 = std::exchange( p1, nullptr );
RVO can often sort out your object return problem just fine. But lifetimes are more subtle. Sometime move semantics can significantly clean up your overall design.
All of eigen was originally based on template expressions, which are very complex. That technique / hack is not necessary if you move data structures. You no longer have to do elaborate work around to avoid copying temporaries.
AFAIK Eigen uses these scary template things to save RAM bandwidth.
When you write x=a+b+c for large vectors, lazy evaluation will produce code that sequentially loads a, b, c, computes the sum in a register, stores to x.
If you’ll use move semantics and temporary variables, the code will be compiled into auto tmp = a+b; x=tmp+c; This code computes the complete tmp variable, stores in RAM, then load again. For simple things like sum or component-wise multiplication of long vectors, RAM is way slower than ALUs.