6 Move Operation

4 minute read

Published:


Motivation

Construct a local array and return by value

  • Will invoke 2 copy constructions
IntArray createIntArray() {
    IntArray tmp;
    for (int i = 0; i < 20; i++) {
        tmp.push_back(i);
        std::cout << tmp << std::endl;
    }
    return tmp;   // copy by value
}

int main() {
    IntArray ia { createIntArray() };  // make another copy
}

Earlier, deleted the copy constructor!

It’s very cumbersome to return an object by value by copying!

  • Want to pass a reference, and allocate the structure outside the function
void createIntArray(IntArray* ia_ptr) {
    for (int i = 0; i < 20; i++) {
        ia_ptr->push_back(i);
        std::cout << *ia_ptr << std::endl;
    }
}

int main() {
	IntArray ia;    // allocate locally on the stack
	createIntArray(&ia); 
}

Move Constructor

Move constructor only kicks in when assigning to a temporary object that will go away

  • More efficient than copy
  1. Do a shallow copy of each element from tmp to the new object this
  2. Sever the connection to the original pointer .a
IntArray(IntArray&& tmp) : sz{tmp.sz}, cap{tmp.cap}, a{tmp.a} {
    tmp.sz = tmp.cap = 0;
    tmp.a = nullptr;
    std::cout << "move ctor" << std::endl;
}

Instead of making a deep copy of original tmp, “steals” what tmp used to have, and move to the return

Now, when tmp -> unnamed return -> ia, each old object gets destroyed

  • Instead of making a brand object and making a copy, steal the internals
  • Two copies becomes two movements
  • Old object gets destroyed, and delete nullptr is okay

Move constructor can be elided as well

&& rvalue Reference

  • Regular variables are lvalue
    • int i = 5,
      • i can be on the LHS, lvalue
      • 5 can’t be lvalue, can’t do 5 = i
  • MyString{"xyz"} is rvalue
struct X {
    X() : d{100} {}   // set X.d to 100
    double d;
};

void f1(X& t)       { t.d *= 2;  cout << t.d << endl; }

void f2(const X& t) { cout << "can't change t" << endl; }

void f3(X&& t)      { t.d *= 3;  cout << t.d << endl; }

int main() {
    X x;           // x is an lvalue
    f1(x);         // passing an lvalue to X&       --> ok
    f2(x);         // passing an lvalue to const X& --> ok
    f3(x);         // passing an lvalue to X&&      --> not ok

	// X{} creates a temp object (rvalue)
    f1( X{} );     // passing an rvalue to X&       --> not ok
    f2( X{} );     // passing an rvalue to const X& --> ok
    f3( X{} );     // passing an rvalue to X&&      --> ok
}
  • Copy constructor take const reference, since old object not modified
  • Move operator take revalue reference to bind to rvalue
    • Only allow stealing from temporary objects
    • Not named!!!

Move Assignment

IntArray& operator=(IntArray&& tmp) {
    if (this != &tmp) {
        delete[] a;

        sz = tmp.sz;
        cap = tmp.cap;
        a = tmp.a;

        tmp.sz = tmp.cap = 0;
        tmp.a = nullptr;
    }
    std::cout << "move assignment" << std::endl;
    return *this;
}

Usage

IntArray ia = { createIntArray() }
IntArray ia2;
ia2 = ia;        // we don't have copy assignment, 

Won’t work since ia is an lvalue, not allowed to move!!

Need to cast to force a move assignment

ia2 = (IntArray&&) ia;     // not recommended
// or 
ia2 = std::move(ia);

Original ia loses its content


Summary

Compiler can generate a move constructor and move assignment

There are some edge cases for that, beyond the scope

  • When no destructor, copy constructor, or copy assignment declared
    • Compatibility with copy rules, if legacy code write copy but not move
    • Move will fall back to copy
      • rvalues can be bound to const lvalues
    • Member-wise move
  • If either move constructor or move assignments are declared, compiler will implicitly delete the copy constructor and copy assignment

Rule of 5

  • If you declare any of destructor/copy/move (including default and delete), very likely need to declare all of them

Rule of 0

  • If all class members already have their 5 defined correctly (E. library class), and you class has no special resource management, no need to declare in your class
  • Some prefer to declare with =default