Home Github hello@fazalkhan.net

Brief notes on C++ copy vs. move semantics

What are lvalues and rvalues

Recently I came across the concepts of lvalues and rvalues. Conceptually, I have understood these concepts to be as follows:

Why these concepts are useful?

This topic is important for efficiency. Often copying data is a costly operation, and it can be advantageous to "move" the data instead.

A simple example showing a copy and move constructor illustrates this point.

Consider the source code below:

#include <iostream>

class Matrix
{
public:
int rows;
int cols;
int size;
double *data;

/* original constructor */
Matrix(int rows, int cols) : rows(rows), cols(cols), size(rows * cols)
{
std::cout << "original constructor" << std::endl;
data = new double[size];
}

/* copy constructor */
Matrix(const Matrix &rhs) : rows(rhs.rows), cols(rhs.cols), size(rhs.rows * rhs.cols), data(new double[size])
{
std::cout << "copy constructor" << std::endl;
std::copy(rhs.data, rhs.data + size, data);
}

/* move constructor */
Matrix(Matrix &&rhs) : rows(rhs.rows), cols(rhs.cols), size(rhs.rows * rhs.cols), data(rhs.data)
{
std::cout << "move constructor" << std::endl;
rhs.rows = 0;
rhs.cols = 0;
rhs.size = 0;
rhs.data = nullptr;
}

~Matrix()
{
delete[] data;
}

void print()
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
std::cout << data[i * cols + j] << "\t";
std::cout << std::endl;
}
}
};

int main()
{
/* create a new matrix on the heap */
Matrix *my_matrix = new Matrix(10, 10);

/* give it some interesting cell values */
for (int i = 0; i < my_matrix->size; i++)
my_matrix->data[i] = i;

/* Now we can try and see the difference between the move and copy semantics */
Matrix copied_matrix(*my_matrix);

/* std::move casts to a rvalue pointer, which in turn means the move constructor in the class is invoked */
Matrix moved_matrix(std::move(*my_matrix));

/* clear the memory */
delete my_matrix;

/* check the copy constructed matrix values */
std::cout << "copied constructed matrix" << std::endl;
copied_matrix.print();

/* check the move constructed matrix values */
std::cout << "moved constructed matrix" << std::endl;
moved_matrix.print();
}

Code Explanation

In this code I create a very simple Matrix class. For simplicity it doesn't do much.

Inside the Matrix class there are three different constructors; original, copy and move.

To invoke the move constructor, we need to just do the following steps:

Once these two steps are fulfilled, the move constructor will be invoked.

Caveats

After we call std::move the new matrix has ownership of the data. For this reason it is important to fix any wayward pointers in the move constructor body.