0

I'm trying to get my head around move semantics. In particular I want to know how to create a 'move only' type. Here's my attempt:

class Movable {
    Movable(const Movable&) = delete;
    Movable &operator=(Movable &) = delete;

public:
    Movable() { }
    Movable(Movable&& rhs) { cout << "mov constructor called" << endl; }
    Movable &operator=(Movable &&rhs) { cout << "Mov assignment called" << endl; return *this; }
};

int main() {

    // (1) This works correctly
    Movable mov_constructored = ([]{
        return Movable();
    })();

    // (2) Why do I have to explicity use std::move?
    Movable mov_assigned = std::move(mov_constructored);

    // (3) The last line fails because it's trying to use the copy constructor.
    // Is it possible to pass by mov?
    mov_assigned = std::move(([](Movable mov_passed){
        return mov_passed;
    })(mov_constructored));
}

My main questions are (1) why is it that #2 requires me to explicitly express that I want to move rather than copy? Seems like a default behavior would be if there is no copy constructor, use the move constructor instead (assuming it exists). The actual behavior seems to just fail unless you explicitly declare move semantics.

Question (2) is essentially what is the proper way to pass a move only object? Is the only correct way to pass by reference (because move semantics only relate to assignment / return values perhaps?) or is there an actual way to 'move' an object into a function?

sircodesalot
  • 11,231
  • 8
  • 50
  • 83
  • 1
    The parameter in your move constructor cannot be moved from because it is const: `Movable(const Movable&& rhs)`. – eerorika Mar 31 '14 at 14:00
  • I changed it, but am not seeing any difference? – sircodesalot Mar 31 '14 at 14:01
  • 2
    It doesn't make a difference yet, because your current implementation of the move constructor doesn't try to move anything but just prints to stream instead. When you actually implement the function, you'll find that it won't compile if the parameter is `const`. – eerorika Mar 31 '14 at 14:04
  • I see, I follow that. Thanks – sircodesalot Mar 31 '14 at 14:05
  • 2
    The answers below made a lot more sense after I understood the difference between lvalue and rvalue. (http://msdn.microsoft.com/en-us/library/f90831hc.aspx) – sircodesalot Mar 31 '14 at 14:14
  • I strongly recommend you to read this: http://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues – Manu343726 Mar 31 '14 at 14:16
  • @Manu343726 I've tried reading that before. Result: `head.asplode()`. – sircodesalot Mar 31 '14 at 14:57

2 Answers2

6

1) Because move_constructed is an lvalue, so it tries to call the lvalue assigment. We use std::move() to cast an lvalue reference to an rvalue reference (Note that std::move() doesn't move, its only a cast).

2) Again, the problem is that move_constructed is an lvalue, so the by-value argument is initialized using its copy ctor. If you pass an rvalue (Or cast an lvalue to an rvalue using std::move()), that call should work, because the by-value parameter is initialized using the move ctor. (See note)

NOTE: Since C++11, I prefer to talk about lvalue constructor and rvalue constructor, instead of classic copy constructor and C++11 move constructor. I think this wording makes simple to understand lvalue vs rvalue/move semantics:

The classic copy ctor is just a constructor which takes an lvalue to initialize an object. Since its an lvalue, we cannot stole its resources, because the lvalue possible will be used later. So we have to copy it.

The move ctor is just a constructor which takes an rvalue to initialize an object. Since its an rvalue, we can safely stole its data/resources, because the rvalue will be going out of scope later.

Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • I thought though that if `move_constructored` was being used in an assignment, then it would be interpreted as an rvalue? – sircodesalot Mar 31 '14 at 14:03
  • Ah, I see. I get it now. This point cleared it up: `Since its an rvalue, we can safely stole its data/resources, because the rvalue will be going out of scope later`. – sircodesalot Mar 31 '14 at 14:08
2

In C++11, it works the other way around -- it falls back to copying if it can't move. Copying is considered to be the safer operation, while moving is considered to be the faster one. So, where applicable, a move will be attempted to get performance, but will fall back to a copy. If you have a statement like:

Movable mov_assigned = mov_constructored;

Then this is explicitly requesting a copy, and a move is not considered a safe alternative to a copy. A C++ programmer would not be expecting mov_constructored to change after that statement.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132