Move semantics when sending object as function's parameter

23,675

Solution 1

An rvalue reference is (surprise:) a reference indeed.

You can move from it, but std::move does not move.

So, if you don't move from it, you'll actually operate on the rvalue object (through the rvalue reference).


The usual pattern would be

void foo(X&& x)
{
    X mine(std::move(x));

    // x will not be affected anymore
}

However, when you do

void foo(X&& x)
{
    x.stuff();
    x.setBooDitty(42);
}

effectively, X&& is just acting as a traditional reference

Solution 2

When you write value = std::move(other.value); you should understand that std::move does not move anything. It just converts its parameter to a rvalue reference, then, if the left hand side has a move constructor/assignment operator, the left hand side deals with it (and how to actually move the object). For plain old types (PODs), std::move doesn't really do anything, so the old value remains the same. You are not "physically" moving anything.

Solution 3

Compare these two functions:

void Bar(Foo&& x) {
    std::cout << "# " << x.value << std::endl;
}

vs.

void Bar(Foo&& x) {
    std::cout << "# " << x.value << std::endl;
    Foo y=std::move(x);
}

Both take an rvalue reference, but only the second calls the move constructor. Consequently, the output of the first is

# 5
5

whereas the output of the second -- since the value of foo is changed -- is:

# 5
1

DEMO


EDIT: This is a question I had as well some time ago. My mistake then was to assume that the creation of an rvalue reference directly invokes the call of a move constructor. But, as was mentioned here before, std::move doesn't do anything at runtime, it just changes the type to an rvalue-reference. The move constructor is only invoked when you "move" your rvalue reference into another object as above.

Solution 4

Bar function should not take reference && (this syntax is valid only in move constructor declaration/definition):

void Bar(Foo x) { ... }  // not "Foo&& x"

To call move constructor of temporary argument object in function Bar:

Bar(  std::move( x )  );

To call copy constructor:

Bar( x );
Share:
23,675
RippeR
Author by

RippeR

Updated on July 09, 2022

Comments

  • RippeR
    RippeR almost 2 years

    I'm playing with move constructors and move assignments and i've stumbled on this problem. First code:

    #include <iostream>
    #include <utility>
    
    class Foo {
        public:
            Foo() {}
    
            Foo(Foo&& other) {
                value = std::move(other.value);
                other.value = 1; //since it's int!
            }
    
            int value;
    
        private:
            Foo(const Foo& other);
    };
    
    
    void Bar(Foo&& x) {
        std::cout << "# " << x.value << std::endl;
    }
    
    int main() {
        Foo foo;
        foo.value = 5;
    
        Bar(std::move(foo));
        std::cout << foo.value << std::endl;
    
        return 0;
    }
    

    To my mind, when i use:

    Bar(std::move(foo));
    

    Program should "move" foo object to temp object created using move constructor in Bar function. Doing so would leave foo object's value equal zero. Unfortunatly it seams that object held in Bar function as parameter is some sort of reference, since it doesnt "move" original value but using Bar's parameter i can change it.

    Would someone mind expalining me why i see in console:

    #5
    5
    

    instead of

    #5
    0 //this should be done by move constructor?
    
  • RippeR
    RippeR over 9 years
    Yeah, i understand it. But what kind of object does Bar() function takes in main() example? Is it reference (which explains why i can change foo object from main())?
  • vsoftco
    vsoftco over 9 years
    @RippeR Bar(Foo&&) takes a rvalue reference to Foo. std::move(foo) transforms the lvalue reference to Foo to a rvalue reference to Foo. In Bar, you are not moving/changing anything, so the object foo is not affected at all. Try adding x.value = -1; as the last line of Bar(Foo&& x) and see what's happening.
  • RippeR
    RippeR over 9 years
    Thanks, second code explains what i didn't realize. Based on your first code take this: ideone.com/3BmMzm Why first line foo() function doesnt work? Second and third use the same type (as seen by latter lines).
  • sehe
    sehe over 9 years
    You didn't define a copy constructor, so you cannot copy-construct a Bar (add Bar(Bar const&) = default; to fix it). (Bar&&)bar is just a (very) unsafe way to write std::move(bar)
  • RippeR
    RippeR over 9 years
    Yea i can read compiler errors. My question is why does it work that way. If i already have rvalue reference given by std::move() then why i have to (again) use std::move to use move constructor (not copy constructor!)? If i had Bar& - ok, then it would try to use Bar(const Bar&) copy constructor. But if i'm providing Bar&& why it wont use Bar(Bar&&) move constructor?
  • sehe
    sehe over 9 years
    Because you can only move from rvalue references... Oh, and yes, the Bar&& value parameter is a lvalue, so you need to cast to rvalue again (using... std::move). C++ is a fun language. You'll get the hang of it.
  • sehe
    sehe over 9 years
    @vsoftco Actually, no. It's just that the && argument itself is a const lvalue. Like int& y; is a const lvalue. But it refers to a mutable int object. Reference collapsing comes into play with type deduction (and yes, that brings universal references into view, but they were solid out of scope for the question and comments)
  • RippeR
    RippeR over 9 years
    Thanks, now i get that Type&& is just another reference. I thought it was meant to construct new element using move constructor. Anyway, thanks for answer! :)
  • vsoftco
    vsoftco over 9 years
    @sehe, I though it also comes into play with typedefs, like typedef int&& my_int_ref, then using it as my_int_ref& x;. Am I wrong? Although the example I gave above is probably wrong.
  • sehe
    sehe over 9 years
    Yeah, that's reference collapsing too. Still out of scope for the comment thread though. Bar&& value is a const lvalue that refers to a Bar object that we don't care about hereafter (hence we promise the compiler it can be treated as a rvalue reference).
  • vsoftco
    vsoftco over 9 years
    @sehe, yes, realized that and deleted :) thanks for the clarification!
  • Teeeeeeeeeeeeeeeeeeeeeeeeeeeej
    Teeeeeeeeeeeeeeeeeeeeeeeeeeeej almost 2 years
    With respect, I downvoted this answer because it's saying what one "should" not do, but the original question is asking why something works the way it does, not whether it's a good practice or not. These kinds of questions are valuable because understanding why a behavior occurs deepens one's understanding of how programming in a language works, and improves our ability to understand what a given string of code is actually going to do when it is compiled and run.