11

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
  • 1,472
  • 1
  • 12
  • 23
  • 1
    See here: [Do built-in types have move semantics?](http://stackoverflow.com/questions/14679605/do-built-in-types-have-move-semantics) – davidhigh Nov 26 '14 at 00:35
  • 4
    It might be educational to compare what happens if you instead have `void Bar(Foo x);`. – aschepler Nov 26 '14 at 00:40
  • 1
    If the function is `Bar(Foo&& x)`, then inside `Bar`, it is *exactly the same* as if the function were `Bar(Foo& x)`. The difference is only in which sorts of objects/references can bind to it when calling the function. – M.M Nov 26 '14 at 00:46
  • @davidhigh: yes, i've already seen it and if you would read carefully you'd know it's not about that... see "value = std::move(other.value); other.value = 1; //since it's int!" code... – RippeR Nov 26 '14 at 00:53
  • @MattMcNabb - thanks, that is what i wanted. I didnt realize that && works like normal reference (well, not excactly but...) and though it should construct object (which is pretty stupid of me if i think about it now). – RippeR Nov 26 '14 at 00:54
  • @RippeR: you're right, that was a bit fast. See my answer. – davidhigh Nov 26 '14 at 00:57

4 Answers4

25

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

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks, second code explains what i didn't realize. Based on your first code take this: http://ideone.com/3BmMzm Why first line foo() function doesnt work? Second and third use the same type (as seen by latter lines). – RippeR Nov 26 '14 at 00:51
  • 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)` – sehe Nov 26 '14 at 00:54
  • 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? – RippeR Nov 26 '14 at 00:57
  • 1
    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 Nov 26 '14 at 00:58
  • @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) – sehe Nov 26 '14 at 01:03
  • @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. – vsoftco Nov 26 '14 at 01:06
  • 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_). – sehe Nov 26 '14 at 01:07
  • @sehe, yes, realized that and deleted :) thanks for the clarification! – vsoftco Nov 26 '14 at 01:08
9

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.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 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())? – RippeR Nov 26 '14 at 00:43
  • 1
    @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. – vsoftco Nov 26 '14 at 00:48
2

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.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • 1
    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! :) – RippeR Nov 26 '14 at 01:03
1

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 );
fider
  • 1,976
  • 26
  • 29
  • 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. – Teeeeeeeeeeeeeeeeeeeeeeeeeeeej Jul 01 '22 at 16:06