Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass by value vs pass by rvalue reference

Tags:

c++

c++11

c++14

When should I declare my function as:

void foo(Widget w);

as opposed to

void foo(Widget&& w);?

Assume this is the only overload (as in, I pick one or the other, not both, and no other overloads). No templates involved. Assume that the function foo requires ownership of the Widget (e.g. const Widget& is not part of this discussion). I'm not interested in any answer outside the scope of these circumstances. See addendum at end of post for why these constraints are part of the question.

The primary difference that my colleagues and I can come up with is that the rvalue reference parameter forces you to be explicit about copies. The caller is responsible for making an explicit copy and then passing it in with std::move when you want a copy. In the pass by value case, the cost of the copy is hidden:

    //If foo is a pass by value function, calling + making a copy:     Widget x{};     foo(x); //Implicit copy     //Not shown: continues to use x locally      //If foo is a pass by rvalue reference function, calling + making a copy:     Widget x{};     //foo(x); //This would be a compiler error     auto copy = x; //Explicit copy     foo(std::move(copy));     //Not shown: continues to use x locally 

Other than that difference. Other than forcing people to be explicit about copying and changing how much syntactic sugar you get when calling the function, how else are these different? What do they say differently about the interface? Are they more or less efficient than one another?

Other things that my colleagues and I have already thought of:

  • The rvalue reference parameter means that you may move the argument, but does not mandate it. It is possible that the argument you passed in at the call site will be in its original state afterwards. It's also possible the function would eat/change the argument without ever calling a move constructor but assume that because it was an rvalue reference, the caller relinquished control. Pass by value, if you move into it, you must assume that a move happened; there's no choice.
  • Assuming no elisions, a single move constructor call is eliminated with pass by rvalue.
  • The compiler has better opportunity to elide copies/moves with pass by value. Can anyone substantiate this claim? Preferably with a link to gcc.godbolt.org showing optimized generated code from gcc/clang rather than a line in the standard. My attempt at showing this was probably not able to successfully isolate the behavior: https://godbolt.org/g/4yomtt

Addendum: why am I constraining this problem so much?

  • No overloads - if there were other overloads, this would devolve into a discussion of pass by value vs a set of overloads that include both const reference and rvalue reference, at which point the set of overloads is obviously more efficient and wins. This is well known, and therefore not interesting.
  • No templates - I'm not interested in how forwarding references fit into the picture. If you have a forwarding reference, you call std::forward anyway. The goal with a forwarding reference is to pass things as you received them. Copies aren't relevant because you just pass an lvalue instead. It's well known, and not interesting.
  • foo requires ownership of Widget (aka no const Widget&) - We're not talking about read-only functions. If the function was read-only or didn't need to own or extend the lifetime of the Widget, then the answer trivially becomes const Widget&, which again, is well known, and not interesting. I also refer you to why we don't want to talk about overloads.
like image 535
Mark Avatar asked Jun 21 '16 04:06

Mark


People also ask

Is pass by value more efficient than pass by reference?

Pass-by-references is more efficient than pass-by-value, because it does not copy the arguments. The formal parameter is an alias for the argument. When the called function read or write the formal parameter, it is actually read or write the argument itself.

What is the difference between pass by value by reference in C and pass by reference in C++?

Unlike in C, where passing by reference was really just passing a pointer by value, in C++ we can genuinely pass by reference.

What are Lvalues and Rvalues in C++?

Lvalues and rvalues are fundamental to C++ expressions. Put simply, an lvalue is an object reference and an rvalue is a value. The difference between lvalues and rvalues plays a role in the writing and understanding of expressions.

Can you pass an lvalue to an rvalue reference?

Explanation: If you pass an lvalue T to enqueue , U will deduce to T& , and the forward will pass it along as an lvalue, and you'll get the copy behavior you want. If you pass an rvalue T to enqueue , U will deduce to T , and the forward will pass it along as an rvalue, and you'll get the move behavior you want.


1 Answers

What do rvalue usages say about an interface versus copying? rvalue suggests to the caller that the function both wants to own the value and has no intention of letting the caller know of any changes it has made. Consider the following (I know you said no lvalue references in your example, but bear with me):

//Hello. I want my own local copy of your Widget that I will manipulate, //but I don't want my changes to affect the one you have. I may or may not //hold onto it for later, but that's none of your business. void foo(Widget w);  //Hello. I want to take your Widget and play with it. It may be in a //different state than when you gave it to me, but it'll still be yours //when I'm finished. Trust me! void foo(Widget& w);  //Hello. Can I see that Widget of yours? I don't want to mess with it; //I just want to check something out on it. Read that one value from it, //or observe what state it's in. I won't touch it and I won't keep it. void foo(const Widget& w);  //Hello. Ooh, I like that Widget you have. You're not going to use it //anymore, are you? Please just give it to me. Thank you! It's my //responsibility now, so don't worry about it anymore, m'kay? void foo(Widget&& w); 

For another way of looking at it:

//Here, let me buy you a new car just like mine. I don't care if you wreck //it or give it a new paint job; you have yours and I have mine. void foo(Car c);  //Here are the keys to my car. I understand that it may come back... //not quite the same... as I lent it to you, but I'm okay with that. void foo(Car& c);  //Here are the keys to my car as long as you promise to not give it a //paint job or anything like that void foo(const Car& c);  //I don't need my car anymore, so I'm signing the title over to you now. //Happy birthday! void foo(Car&& c); 

Now, if Widgets have to remain unique (as actual widgets in, say, GTK do) then the first option cannot work. The second, third and fourth options make sense, because there's still only one real representation of the data. Anyway, that's what those semantics say to me when I see them in code.

Now, as for efficiency: it depends. rvalue references can save a lot of time if Widget has a pointer to a data member whose pointed-to contents can be rather large (think an array). Since the caller used an rvalue, they're saying they don't care about what they're giving you anymore. So, if you want to move the caller's Widget's contents into your Widget, just take their pointer. No need to meticulously copy each element in the data structure their pointer points to. This can lead to pretty good improvements in speed (again, think arrays). But if the Widget class doesn't have any such thing, this benefit is nowhere to be seen.

Hopefully that gets at what you were asking; if not, I can perhaps expand/clarify things.

like image 195
Altainia Avatar answered Sep 20 '22 10:09

Altainia