Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is passing by value (if a copy is needed) recommended in C++11 if a const reference only costs a single copy as well?

I am trying to understand move semantics, rvalue references, std::move, etc. I have been trying to figure out, by searching through various questions on this site, why passing a const std::string &name + _name(name) is less recommended than a std::string name + _name(std::move(name)) if a copy is needed.

If I understand correctly, the following requires a single copy (through the constructor) plus a move (from the temporary to the member):

Dog::Dog(std::string name) : _name(std::move(name)) {} 

The alternative (and old-fashioned) way is to pass it by reference and copy it (from the reference to the member):

Dog::Dog(const std::string &name) : _name(name) {} 

If the first method requires a copy and move both, and the second method only requires a single copy, how can the first method be preferred and, in some cases, faster?

like image 926
John Bonata Avatar asked Sep 03 '17 23:09

John Bonata


People also ask

Why is passing by reference preferred for passing an object to a function?

If you're calling a function that needs to take a large object as a parameter, pass it by const reference to avoid making an unnecessary copy of that object and taking a large efficiency hit. If you're writing a copy or move constructor which by definition must take a reference, use pass by reference.

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.

Does passing by value make a copy?

By definition, pass by value means you are making a copy in memory of the actual parameter's value that is passed in, a copy of the contents of the actual parameter.

What is the principal reason for passing arguments by reference?

Pass by reference allows us to pass arguments to a function without making copies of those arguments each time the function is called.


2 Answers

When consuming data, you'll need an object you can consume. When you get a std::string const& you will have to copy the object independent on whether the argument will be needed.

When the object is passed by value the object will be copied if it has to be copied, i.e., when the object passed is not a temporary. However, if it happens to be a temporary the object may be constructed in place, i.e., any copies may have been elided and you just pay for a move construction. That is, there is a chance that no copy actually happens.

like image 125
Dietmar Kühl Avatar answered Oct 14 '22 03:10

Dietmar Kühl


Consider calling the various options with an lvalue and with an rvalue:

  1. Dog::Dog(const std::string &name) : _name(name) {} 

    Whether called with an lvalue or rvalue, this requires exactly one copy, to initialize _name from name. Moving is not an option because name is const.

  2. Dog::Dog(std::string &&name) : _name(std::move(name)) {} 

    This can only be called with an rvalue, and it will move.

  3.  Dog::Dog(std::string name) : _name(std::move(name)) {} 

    When called with an lvalue, this will copy to pass the argument and then a move to populate the data member. When called with an rvalue, this will move to pass the argument, and then move to populate the data member. In the case of the rvalue, moving to pass the argument may be elided. Thus, calling this with an lvalue results in one copy and one move, and calling this with an rvalue results in one to two moves.

The optimal solution is to define both (1) and (2). Solution (3) can have an extra move relative to the optimum. But writing one function is shorter and more maintainable than writing two virtually identical functions, and moves are assumed to be cheap.

When calling with a value implicitly convertible to string like const char*, the implicit conversion takes place which involves a length computation and a copy of the string data. Then we fall into the rvalue cases. In this case, using a string_view provides yet another option:

  1. Dog::Dog(std::string_view name) : _name(name) {} 

    When called with a string lvalue or rvalue, this results in one copy. When called with a const char*, one length computation takes place and one copy.

like image 39
Jeff Garrett Avatar answered Oct 14 '22 02:10

Jeff Garrett