2.2.2 std::move, the Subtleties

Naming quality: ★☆☆☆☆. Would have put zero stars if I could.

As a great example of “naming things is hard”, std::move does not move anything. It actually just marks the value as transferable, by casting it to an rvalue-reference. See this actual implementation exctracted from GCC 9:

template<typename _Tp> 
constexpr typename std::remove_reference<_Tp>::type&& 
move(_Tp&& __t) noexcept 
{ 
  return static_cast 
  < 
    typename std::remove_reference<_Tp>::type&& 
  >(__t); 
}

Guideline

As a rule of thumb of its usage, consider two use cases:

  1. If callee always takes ownership of the value: prefer arguments passed by value.
  2. If callee may take ownership of the value: use an rvalue reference.
// "I need my own bar." 
void foo_copy(bar b) { /* ... */ } 
 
// "I may take ownership of b... 
//  ...Unless I don’t." 
void foo_rvalue(bar&& b) { /* ... */ } 
 
void baz() 
{ 
  bar b1; 
  // Valid, b1 is unchanged in baz. 
  foo_copy(b1); 
 
  // Valid, we can forget about b1 now. 
  foo_copy(std::move(b1)); 
 
  bar b2; 
  // Invalid, b2 is not an rvalue 
  // foo_rvalue(b2); 
 
  // Valid, but is b2 actually moved? 
  foo_rvalue(std::move(b2)); 
}

Note that the rules above do not apply directly for functions that assign an existing resource, like a setter. In this case, always passing by value may be a pessimization if the caller does not pass an rvalue and the member assigned to releases its resource before taking the one from the argument.

// This example does not consider small string optimization. 
 
class foo 
{ 
  std::string name; 
 
public: 
  void set_name_value(std::string n) 
  { 
    // The internals of the name member are released, then the resources from 
    // n are transfered. 
    name = std::move(n); 
  } 
 
  void set_name_reference(const std::string& n) 
  { 
    // The value in n is copied in this->name. No allocation is done if there 
    // is enough room for it. 
    name = n; 
  } 
}; 
 
void test_foo() 
{ 
  // Set up. 
  foo f; 
  f.set_name_value("abc"); 
 
  // Behind the following call: 
  // 1. allocate a temporary std::string on the stack. 
  // 2. allocate the internal buffer of the temporary in the heap. 
  // 3. copy "def" in the internal buffer. 
  // 4. swap f.name’s internals with the ones of the temporary. 
  // 5. release the temporary’s internals (previously f.name’s). 
  f.set_name_value("def"); 
 
  // Behind the following call: 
  // 1. allocate a temporary std::string on the stack. 
  // 2. allocate the internal buffer of the temporary in the heap. 
  // 3. copy "ghi" in the internal buffer. 
  // 4. copy the temporary’s internals into f.name. 
  // 5. release the temporary’s internals. 
  f.set_name_reference("ghi"); 
 
  // Behind the following call: 
  // 1. allocate a temporary std::string on the stack. 
  // 2. allocate the internal buffer of the temporary in the heap. 
  // 3. copy the internals of name in the temporary’s internals. 
  // 4. swap f.name’s internals with the ones of the temporary. 
  // 5. release the internals of the temporary (previously f.name’s). 
  f.set_name_value(name); 
 
  // Behind the following call: 
  // 1. check that f.name is large enough for name, realloc if needed. 
  // 2. copy the internals of name in f.name. 
  f.set_name_reference(name) 
}

In all cases in the example above, it is better to pass by address. If the allocation of the temporaries is an issue, one can decide to override the setter to take an rvalue reference.