Move Semantics


Definination of std::move

template<class T>
constexpr remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<remove_reference_t<T>&&>(t)
}

std::move doesn’t move anything. It converts any expression into an rvalue so it can be bound to an rvalue reference.

RValue References Guideline

  • No rvalue reference to const type
    • Use a non-const rvalue reference instead.
    • Most uses of rvalue references modify the object being referenced
    • Most rvalues are not const
  • No rvalue reference as function return type
  • Rvalue references often bind to temporaries, which don’t outlive the function

std::move Guidelines

  • Next operation after std::move is destruction or assignment
    • The object that was moved from shouldn’t be used again
  • Don’t std::move the return of a local variable
    • Core Guideline F.48: Don’t return std::move(local)
    • C++ Standard has a special rule for this
      • The return expression is an rvalue if it is a local variable of parameter
      std::string func(std::string param, std::string* ptr){
          std::string local = "Hello";
          *ptr = local;
          if(some_condition()){
              return local; //rvalue, calls the move constructor
          }
          return *ptr; //lvalue, calls the copy constructor
      }
    

Move Constructor

  • Implicitly declared move constructor if there no user-defined
    • destructor
    • copy constructor
    • copy assignment operator
    • move assignment operator
  • Implicitly declared or explicitly use =default to let compiler generate move constructor for you
    • Move constructs each base and non-static data member
    • Deleted if any base or non-static data member cannot be move constructed
struct s {
    double* data;
    s( s&& other) noexcept 
        : data(std::move(other.data)){
        other.data = nullptr;
    }
    //a more elegant way is using std::exchange
    s (s && other) noexcept
        : data(std::exchange(other.data, nullptr))
    {}
};

Move Assignment Operator

  • Implicitly declared move constructor if there no user-defined
    • destructor
    • copy constructor
    • copy assignment operator
    • move constructor
  • Implicitly declared or explicitly use =default to let compiler generate move assignment operator for you
    • Move assigns each base and non-static data member
    • Deleted if any base or non-static data member cannot be move assigned
struct s {
    double* data;
    S& operator=(S&& other) noexcept{
        if(this == &other) return *this;
        delete[] data;
        data = std::exchange(other.data, nullptr)
        return *this;
    }
};

Move Copy/Assignment Guidelines:

  • Move constructor/assignment should be explicitly noexcept
    • Core Guideline C.66: Make move operators no except
    • Moves are supposed to transfer resources, not allocate or acquire resources. No exceptions should be thrown.
    • Declare it noexcept even when it is defined as default
      • foo(foo&&) noexcept = default
  • Move-from object must be left in a valid state
    • Core Guideline C.64: A move operation should move and leave its source in a valid state
    • Prefer to leave it in the default constructed state
      • But that is not always practical
      struct s{
          std::string str;
          std::size_t len;
          //Invariant: len == str.length()
          s(s&& other) noexcept : str(std::move(other.str)),
                                  len(std::move(other.len)){
              //reset other to a valid state
              other.str.clear();
              other.len = 0;
          }
      }
    

Summary

  • Use =default when possible
    • Core Guideline C.80: Use =default if you have to be explicit about using the default semantics
  • Rule of 5 / Rule of 0
    • Core Guideline C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all
      • destructor
      • copy constructor
      • copy assignment operator
      • move constructor
      • move assignment operator
    • Rule of 0: If default behavior is correct for all five, let compiler do everything
    • Rule of 1: If you must define one of the five, declare all of them explicitly

Resources