Advantages of pass-by-value and std::move over pass-by-reference
- Did I understand correctly what is happening here?
Yes.
- Is there any upside of using
std::move
over passing by reference and just callingm_name{name}
?
An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string&
reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name
and const std::string&
arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue
std::string nameString("Alex");
Creature c(nameString);
to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function
std::string nameString("Alex");
Creature c(std::move(nameString));
causes two move constructions. In contrast, when the function parameter is const std::string&
, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string
).
But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):
void setName(std::string name)
{
m_name = std::move(name);
}
will cause a deallocation of the resource that m_name
refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.
Pass-by-value and std::move vs forwarding reference
But it seems to me that passing by either const reference or rvalue reference can save a copy in some situations.
Indeed, but it requires more overloads (and even worst with several parameters).
Pass by value and move idiom has (at worst) one extra move. which is a good trade-off most of the time.
maybe using a forwarding reference to avoid writing both constructors.
Forwarding reference has its own pitfalls:
- disallows
{..}
syntax for parameter as{..}
has no type.
would not be possible.Test2 a({5u, '*'}); // "*****"
- is not restrict to valid types (requires extra
requires
or SFINAE).
would produces error inside the constructor, and not at call site (so error message less clear, and SFINAE not possible).Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
- for constructor, it can take precedence over copy constructor (for non-const l-value)
would produce error, asTest2 c(a); // Call Test2(T&&) with T=Test2&
// instead of copy constructor Test2(const Test2&)std::string
cannot be constructed fromTest2&
.
IDE recommends 'pass by value and use std move' when parameter class has no move constructor
Your Person
class is following the "rule-of-zero", meaning it doesn't declare any copy/move operations or destructor explicitly.
That is usually the correct thing to do, because then the compiler will declare all of them implicitly and define them with the semantics you would usually expect from the these operations, if that is possible.
In your case, all members of Person
can be move-constructed, so the implicit move constructor of Person
will be defined to move-construct the members one-by-one.
This is what is used when you add std::move
in mData(std::move(data))
. Without it data
is a lvalue and instead the implicit copy constructor which copy-constructs each member would be used.
Copy-constructing a std::string
is often more costly than move-constructing it, which is probably why the tool warns about that case, but not if std::string
isn't there. Move-constructing and copy-constructing an int
member is exactly the same operation.
If you don't want to check each time what members a class has, you can simply always use std::move
in these situations. For example changing mId(id)
to mId(std::move(id))
is pointless, because id
is a scalar type, but it also doesn't make anything worse.
Is the pass-by-value-and-then-move construct a bad idiom?
Expensive-to-move types are rare in modern C++ usage. If you are concerned about the cost of the move, write both overloads:
void set_a(const A& a) { _a = a; }
void set_a(A&& a) { _a = std::move(a); }
or a perfect-forwarding setter:
template <typename T>
void set_a(T&& a) { _a = std::forward<T>(a); }
that will accept lvalues, rvalues, and anything else implicitly convertible to decltype(_a)
without requiring extra copies or moves.
Despite requiring an extra move when setting from an lvalue, the idiom is not bad since (a) the vast majority of types provide constant-time moves and (b) copy-and-swap provides exception safety and near-optimal performance in a single line of code.
Passing a parameter to a function with std::move, is there a difference if that parameter is declared as pass-by-value or with a move operand &&?
ValueClass
requires the move constructor of std::string
to be called twice: Once to create the obj
parameter and then to initialize the m_obj
member.
MoveClass
requires only one move constructor of std::string
to be called: obj
is an (rvalue) reference to b
(no new std::string
object is constructed here). Only the m_obj
initialization involves the std::string
move constructor.
There is still the question whether it would be allowed to elide any of these calls (as an exception to the as-if rule). That is not the case though; neither member initialization nor function parameter initialization are a valid context for eliding constructors (or temporaries): https://en.cppreference.com/w/cpp/language/copy_elision
Now, in the given code, there are not necessarily side effects that require the compiler to keep any of the constructor calls. In fact, if you don't perform the reassignment in main
, gcc
notices that the entire program has no side effect and can be optimized to a no-op.
https://godbolt.org/z/hxrVfC
You also didn't give MSVC
the correct parameters. Highest optimization is /O2
, not -O3
(although -O2
does work). Mind the compiler warnings!
Is it better in C++ to pass by value or pass by reference-to-const?
It used to be generally recommended best practice1 to use pass by const ref for all types, except for builtin types (char
, int
, double
, etc.), for iterators and for function objects (lambdas, classes deriving from std::*_function
).
This was especially true before the existence of move semantics. The reason is simple: if you passed by value, a copy of the object had to be made and, except for very small objects, this is always more expensive than passing a reference.
With C++11, we have gained move semantics. In a nutshell, move semantics permit that, in some cases, an object can be passed “by value” without copying it. In particular, this is the case when the object that you are passing is an rvalue.
In itself, moving an object is still at least as expensive as passing by reference. However, in many cases a function will internally copy an object anyway — i.e. it will take ownership of the argument.2
In these situations we have the following (simplified) trade-off:
- We can pass the object by reference, then copy internally.
- We can pass the object by value.
“Pass by value” still causes the object to be copied, unless the object is an rvalue. In the case of an rvalue, the object can be moved instead, so that the second case is suddenly no longer “copy, then move” but “move, then (potentially) move again”.
For large objects that implement proper move constructors (such as vectors, strings …), the second case is then vastly more efficient than the first. Therefore, it is recommended to use pass by value if the function takes ownership of the argument, and if the object type supports efficient moving.
A historical note:
In fact, any modern compiler should be able to figure out when passing by value is expensive, and implicitly convert the call to use a const ref if possible.
In theory. In practice, compilers can’t always change this without breaking the function’s binary interface. In some special cases (when the function is inlined) the copy will actually be elided if the compiler can figure out that the original object won’t be changed through the actions in the function.
But in general the compiler can’t determine this, and the advent of move semantics in C++ has made this optimisation much less relevant.
1 E.g. in Scott Meyers, Effective C++.
2 This is especially often true for object constructors, which may take arguments and store them internally to be part of the constructed object’s state.
Pass by value vs pass by rvalue reference
The rvalue reference parameter forces you to be explicit about copies.
Yes, pass-by-rvalue-reference got a point.
The rvalue reference parameter means that you may move the argument, but does not mandate it.
Yes, pass-by-value got a point.
But that also gives to pass-by-rvalue the opportunity to handle exception guarantee: if foo
throws, widget
value is not necessary consumed.
For move-only types (as std::unique_ptr
), pass-by-value seems to be the norm (mostly for your second point, and first point is not applicable anyway).
EDIT: standard library contradicts my previous sentence, one of shared_ptr
's constructor takes std::unique_ptr<T, D>&&
.
For types which have both copy/move (as std::shared_ptr
), we have the choice of the coherency with previous types or force to be explicit on copy.
Unless you want to guarantee there is no unwanted copy, I would use pass-by-value for coherency.
Unless you want guaranteed and/or immediate sink, I would use pass-by-rvalue.
For existing code base, I would keep consistency.
Pass by value/reference for a parameter that will be moved within function
If your function will definitely move from the given value, then the best way to express this is by taking an rvalue-reference parameter (T&&
). This way, if the user tries to pass an lvalue directly, they will get a compile error. And that forces them to invoke std::move
directly on the lvalue, which visibly indicates to the reader that the value will be moved-from.
Using an lvalue-reference is always wrong. Lvalue-references don't bind to xvalues and prvalues (ie: expressions where it's OK to move from them), so you're kind of lying with such an interface.
Related Topics
Is There a Working C++ Refactoring Tool
Programmatically Reading a Web Page
Default Argument in the Middle of Parameter List
Why Don't the Std::Fstream Classes Take a Std::String
Cuda How to Get Grid, Block, Thread Size and Parallalize Non Square Matrix Calculation
What's the Most Reliable Way to Prohibit a Copy Constructor in C++
Implicit Conversion from Char** to Const Char**
How to Build Cmake Externalproject While Configurating Main One
Sorting a Std::Vector<Std::Pair<Std::String,Bool>> by the String
Advantages of Pass-By-Value and Std::Move Over Pass-By-Reference
C++ Templates Specialization Syntax
Pointer-To-Pointer Dynamic Two-Dimensional Array
How to Dynamically Allocate a Matrix
Getline() Does Not Work If Used After Some Inputs
Why Does Libc++'s Implementation of Std::String Take Up 3X Memory as Libstdc++
Overloaded Functions Are Hidden in Derived Class
How to Read Output from Cmd.Exe Using Createprocess() and Createpipe()