Why doesn't C++ move construct rvalue references by default?
with your design:
void doWork(Widget && param)
{
Widget store1 = param; // automatically move param
Widget store2 = param; // boom
Widget store_last = param; // boom
}
with current design:
void doWork(Widget && param)
{
Widget store1 = param; // ok, copy
Widget store2 = param; // ok, copy
Widget store_last = std::move(param); // ok, param is moved at its last use
}
So the moral here is that even if you have an rvalue reference you have a name for it which means you can use it multiple times. As such you can't automatically move it because you could need it for a later use.
Now let's say you want to re-design the language so that the last use is automatically treated as an rvalue.
This can be easily done in the above example:
void doWork(Widget && param)
{
Widget store1 = param; // `param` treated as lvalue here, copy
Widget store2 = param; // `param` treated as lvalue here, copy
Widget store_last = param; // `param` treated as rvalue here, move
}
Let's ignore the inconsistency of how param
is treated (which in itself is a problem).
Now think what use of param
is the last use:
void doWork(Widget && param)
{
Widget store2 = param; // this can be last use or not
while (some_condition())
{
Widget store1 = param; // this can be both last use and not
}
}
The language simply cannot be designed this way.
C++ Why does returning rvalue reference change caller's behavior when function signature does not return rvalue reference?
What's happening is you are seeing elision there. You are move-constructing on return std::move(x)
with a simple type of Bar
; then the compiler is eliding the copy.
You can see the non-optimized assembly of GetBarRValue
here. The call to the move constructor is actually happening in the GetBarRValue
function, not upon returning. Back in main
, it's just doing a simple lea
, it's not at all calling any constructor.
Why isn't the move constructor being called?
Why doesn't
Class obj2(obj1.fun());
call the move constructor
Because of Copy Elison. The compiler sees that fun()
returns a temporary object, and that temporary will only be used to initialize obj2
, so the compiler optimizes the creation of obj2
by eliminating the temporary object altogether and allowing obj2
to be created directly inside of fun()
itself, thus there is no need for a copy/move operation when fun()
exits.
Class obj2(std::move(obj1.fun()));
does call the move constructor
Because you are forcing it with the explicit std::move
type-cast, so the compiler can't optimize the creation of obj2
via Copy Elison, so it has to allow fun()
to return a temporary object, which you are then moving into the obj2
constructor.
obj3 = obj2.fun()
calls the (move) assignment operator
Because obj3
already exists before the assignment.
without needing to write
std::move(obj2.fun())
Because fun()
returns a temporary object, which is an rvalue, so there is no need to explicitly type-cast it to an rvalue when calling a move assignment operator.
When should we declare rvalue refs if they actually need std::move?
My question is, what's the utility of defining a variable as A&&? I could perfectly define it as A& and move it exactly the same.
If a named variables is passed into a function and modified like that without warning it's very displeasing, and can lead to unpleasant debugging sessions.
When you take by rvalue reference the type system enforces useful guarantees. Either the reference is bound to an rvalue, and so modifying it isn't going to violate anyone's assumptions (it's gone soon anyway). Or you were given explicit permission to move out of it. The calling context can't accidentally give you permission to modify an lvalue, it must do so with an explicit cast (std::move
).
Explicit is better than implicit. And having rvalue references interact with the type system the way they do helps make sure moving does not leave objects in an unexpected state. A glance at the calling context reveals that the variable passed into the function may be left with an unknown state, because it's explicitly surrounded with a call to std::move
.
That's the benefit of rvalue references.
Why is `T(const T&&)` called a move constructor?
Here are some of the differences between move constructors and other constructors:
- Move constructors can be defaulted
- Move constructors don't prevent a type from being a "literal type"
- Non-trivial move constructors prevent a type from being a "trivially copyable type"
- Move constructors prevent the implicit move constructor from being generated
- Move constructors may be automatically called by standard library functions
As far as I can tell, for all of those, not calling X(const X &&)
a move constructor gives undesirable results.
You give an alternative: it might be called a copy constructor instead. That too seems to have undesirable results: it would suppress the implicit copy constructor.
Whether a move constructor actually moves doesn't matter. A POD type may have a move constructor too. It'll just be making a copy, but it's still called a move constructor.
Why rvalue references are connected with move semantics?
Consider the problem. There are two basic move operations we want to support: move "construction" and move "assignment". I use quotations there because we don't necessarily have to implement them with constructors or move assignment operators; we could use something else.
Move "construction" means creating a new object by transferring the contents from an existing object, such that deleting the old object doesn't deallocate resources now used in the new one. Move "assignment" means taking a pre-existing object and transferring the contents from an existing object, such that deleting the old object doesn't deallocate resources now used in the new one.
OK, so these are the operations we want to do. Well, how to do it?
Take move "construction". While we don't have to implement this with a constructor call, we really want to. We don't want to force people to do two-stage move construction, even if it's behind some magical function call. So we want to be able to implement movement as a constructor. OK, fine.
Here's problem 1: constructors have no names. Therefore, you can only differentiate them based on argument types and overloading resolution. And we know that the move constructor for an object of type T
must take an object of type T
as a parameter. And since it only needs one argument, it therefore looks exactly like a copy constructor.
OK, so now we need some way to satisfy overloading. We could introduce some standard library type, a std::move_ref
. It would be like std::reference_wrapper
, but it would be a distinct type. Therefore, you could say that a move constructor is a constructor that takes a std::move_ref<T>
. Alright, fine: problem solved.
Only not; we now have new problems. Consider this code:
std::string MakeAString() { return std::string("foo"); }
std::string data = MakeAString();
Ignoring elision, C++11's expression value category rules state that a type which is returned from a function by value is an prvalue. And therefore, it will automatically be used by move constructors/assignment operators wherever possible. No need for std::move
or the like.
To do it your way would require this:
std::string MakeAString() { return std::move(std::string("foo")); }
std::string data = std::move(MakeAString());
Both of those std::move
calls would be needed to avoid copying. You have to move out of the temporary and into the return value, and then move out of the return value and into data
(again, ignoring elision).
If you think that this is merely a minor annoyance, consider what else rvalue references buy us: perfect forwarding. Without the special reference-collapsing rules, you could not write a proper forwarding function that forwards copy and move semantics perfectly. std::move_ref
would be a real C++ type; you couldn't just slap arbitrary rules like reference collapsing onto it like you can with rvalue references.
At the end of the day, you need some kind of language construct in place, not merely a library type. By making it a new kind of reference, you get to be able to define new rules for what can bind to that reference (and what cannot). And you get to define special reference-collapsing rules that make perfect forwarding possible.
Confusion with move constructors: unable to call move constructor
The reason why you don't see any output from line 3 is that it declares a function, not a variable. This is due to an ambiguity called Most Vexing Parse.
Compare c s3(c())
with int foo(int ())
, which, thanks to implicit type adjustments, is the same as int foo(int (*f)())
.
To get around this, use brace initialisation (which was actually introduced in C++11 partly for this very reason):
c s3(c{});
// or
c s3{c()};
// or
c s3{c{}};
Lvalue reference constructor is called instead of rvalue reference constructor
Why F(F&) constructor is called instead of F(F&&) constructor in constructor of class G?
Because f
is an lvalue. Even though it is bound to an rvalue, and its type is rvalue reference to F
, it is also a named variable. That makes it an lvalue. Don't forget that the value category of an object is not determined by its type, and vice versa.
When you pass an lvalue to a function, only lvalue references can be bound to it. You should change your code as follows if you want to catch rvalues only:
class G {
F f_;
public:
G(F&& f) : f_(std::move(f)) {
std::cout << "G()" << std::endl;
}
};
Alternatively, you could use std::forward<>()
, which is equivalent in this case, but makes your intent of forwarding f
even clearer:
class G {
F f_;
public:
G(F&& f) : f_(std::forward<F>(f)) {
std::cout << "G()" << std::endl;
}
};
Now this last definition is easy to extend so that both lvalues and rvalues of type F
can be bound to the parameter f
:
class G {
F f_;
public:
template<typename F>
G(F&& f) : f_(std::forward<F>(f)) {
std::cout << "G()" << std::endl;
}
};
This allows, for instance, to construct an instance of G
this way:
F f;
G g(f); // Would not be possible with a constructor accepting only rvalues
This last version has a caveat though: your constructor will basically work as a copy-constructor as well, so you might want to explicitly define all the possible copy constructors to avoid awkward situations:
class G {
F f_;
public:
template<typename F>
G(F&& f) : f_(std::forward<F>(f)) {
std::cout << "G()" << std::endl;
}
G(G const&) = default;
G(G&); // Must be defaulted out-of-class because of the reference to non-const
};
G::G(G&) = default;
Since non-template functions are preferred over instantiations of function templates, the copy constructor will be selected when constructing a G
object from another G
object. The same applies, of course, to the move constructor. This is left as an exercise.
What happens when std::move is called on a rvalue reference?
string constructed = s;
This does not cause a move because s
is not an rvalue. It is an rvalue reference, but not an rvalue. If it has a name, it is not an rvalue. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.
string constructed = std::move(s);
This causes a move because std::move(s)
is an rvalue: it's a temporary and its type is not lvalue reference.
There are no other moves in the program (std::move
is not a move, it's a cast).
Related Topics
Pass by Reference and Value with Pointers
What Encoding Does Std::String.C_Str() Use
How Computer Does Floating Point Arithmetic
How to Call Derived Class Method from Base Class Pointer
Is It Bad Practice to Allocate Memory in a Dll and Give a Pointer to It to a Client App
Printf Rounding Behavior for Doubles
Write and Read String to Binary File C++
Boost::Spirit Expression Parser
Boost.Python Not Supporting Parallelism
How to Pass Command Line Arguments to a C Program
How to Do Input Validation in C++ with Cin
C++ Boost: What's the Cause of This Warning
Std::Map Default Value for Build-In Type
How to Know If a Type Is a Specialization of Std::Vector
How to Break When a Specific Exception Type Is Thrown in Gdb
Why Doesn't Reinterpret_Cast Force Copy_N for Casts Between Same-Sized Types