Move semantics (C++)

1 minute read

The code in this post is taken from this video. I just transcripted it here for the purpose of my easy accessing of the concepts explained in the video above.

Example 1

Let’s say we have a std::vector of ints named v1 that we want to initialize with another vector that will expire when it goes outside of its scope. In such a case, instead of using copy assignment to initialize v1 with v2, we can be more efficient using a move assignment to steal the resources from v2 and put them into v1.

// move semantics
#include <utility>
#include <vector>

int main()
{
  std::vector<int> v1;
  {
    std::vector<int> v2 = { 1, 2, 3 };
    v1 = std::move(v2);
  }
  return 0;
}

Example 2

Another (perhaps more interesting) example. Say we have a class Foo that has lots of member variables to initialize (thus being really expensive to initialize an object of this class) and we want to fill a vector of type Foo. We can use std::move to cast an lvalue to an rvalue (see discussion).

// move semantics
#include <utility>
#include <vector>

struct Foo
{
  // a lot of member variables
}

int main()
{
  std::vector<Foo> v;
  {
    Foo f1; // f1 is an lvalue
    v.push_back(f1); // make a copy of foo
    v.push_back(std::move(f1)); // move foo (cast to rvalue (more specifically to an xvalue, as opposed to a prvalue))
    v.push_back(Foo()); // move foo (as a prvalue)
  }
  return 0;
}

In the above example we will have a default constructor call (Foo f1) and then a move constructor call (move(f1)). We can make this more efficient by using perfect forwarding. Check this post to learn more about perfect forwarding.

Example 3

A more complete example (TODO: explain)

// move semantics
#include <utility>
#include <vector>

using namespace std;

struct Foo
{
  Foo(vector<int>&& v) // is actually an lvalue within the scope of this function
  {}

  vector<int> v;
  // Foo has a lot of member variables 
}

void FillVector(vector<int>& v) { /*conditionally fills v*/}

int main()
{
  vector<int> v;
  FillVector(v);

  if (!v.empty()) {
    Foo f1(move(v));
    // valid but unspecified state
    // v.front();	// not safe! Because we're in an unspecified state
    v.clear(); 		// this is fine, since it doesn't depend on the ste
  }
  return 0;
}

Updated: