4

In the line commented by ***, why is Bar's copy constructor called? input_bar is a rvalue reference, so I expect the move constructor to be called. Did it convert to an lvalue reference? I can make the move constructor call if I change that line to bar_(std::move(input_bar)).

#include <iostream>
#include <array>
#include <memory>

class Bar
{
public:   
  Bar(const Bar& bar)
  {
    std::cout << "copy constructor called" << std::endl;
  }

  Bar(Bar&& bar)
  {
    std::cout << "move constructor called" << std::endl;
  }
};

class Foo
{
public:
  Foo(Bar&& input_bar) :
    bar_(input_bar) // ***
  {
  }
  Bar bar_;
};

int main()
{
  Bar bar;
  Foo foo(std::move(bar));
  return 0;
}
Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
Agrim Pathak
  • 3,047
  • 4
  • 27
  • 43

2 Answers2

9

Once an entity has a name, it is clearly an lvalue! If you have a name for an rvalue reference, the entity with the name is not an rvalue but an lvalue. The entire point is that you know that this entity references an rvalue and you can legitimately move its content.

If you want to just forward the rvalueness to the next function you call, you'd use std::move(), e.g.:

Foo(Bar&& bar): bar_(std::move(bar)) {}

Without the std::move() the rvalue is considered to be owned by the constructor. With the std::move() it releases the ownership and passes it on to the next function.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
2

You have to move rhrs:

  Foo(Bar&& input_bar) :
    bar_(std::move(input_bar)) // ***
  {
  }

The reasoning is that once we actually use the RHR, it should be semantially treated as out of scope. Forcing you to use std::move allows the following code to not be undefined:

 Foo(Bar&& input_bar) {
    std::cout << input_bar.baz << std::endl;//not undefined
    bar_ = Bar{std::move(input_bar)};//input_bar is now essentailly destroyted
    //std::cout << input_bar.baz << std::endl;//Undefined behavior, don't do this
 }

The general rule of thumb is that only things without names can actually be used as RHR's...RHR as arguments have names, and thus will be treated as LHR untill you call a function on them.

IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • Yes, as the OP already found out without this answer. The OP is asking for an explanation of why that is necessary, why that effect is not achieved unless `std::move` is used. –  Dec 18 '14 at 21:48
  • Whether that last line produces undefined behavior depends upon what `baz` is. For most types, I would not expect that to be undefined behavior, just unspecified. – Benjamin Lindley Dec 18 '14 at 22:08
  • Right, to add to @BenjaminLindley's comment, there are no standard library types that are no longer usable once moved from, and as far as I'm aware, most user types follow the same design. For such types, after `bar_ = Bar{std::move(input_bar)};` (or better yet, just `bar_ = std::move(input_bar);`), `input_bar` is left in an unspecified valid state. For something like a list, that usually means it's emptied, but inspecting the list is fine, and explicitly clearing it and adding new elements to it is fine as well. –  Dec 20 '14 at 13:22