Michael Rizkalla
by Michael Rizkalla

Since C++11, move semantics were introduced which added potential to speed up our C++ code significantly. In this article, We’ll take a look at move constructors, move assignments.

What does ‘Move’ means in C++ context?

Move instructs the compiler to literally move the resources of an object to another object of the same base type, without re-allocating additional memory. This is particulary useful in dealing with objects that holds chunks of memory allocated on the heap.

Lets assume having a smart Buffer class using RAII (Resource Acquisition Is Initialization) principles that holds a storage allocated on the heap.

// Buffer class accorrding to the rule of three (pre C++11)
class Buffer {
  public:
    // Constructs a buffer with specific size
    Buffer(size_t size) {
        mData = new char[size + 1];
        mData[size] = '\0';
        mSize = size;
    }
    // Copy constructor
    Buffer(const Buffer& rhs) {
        mData = new char[rhs.mSize + 1];
        mSize = rhs.mSize;
        std:memcpy(mData, rhs.mData, mSize + 1);
    }
    // Copy Assignment
    Buffer& operator=(const Buffer& rhs) {
        mData = new char[rhs.mSize + 1];
        mSize = rhs.mSize;
        std:memcpy(mData, rhs.mData, mSize + 1);
        return *this;
    }
    // Deletes allocated data
    ~Buffer() {
        delete[] mData;
    }
    // Print contents
    void print() {
        std::cout << mData << '\n';
    }

    // Buffer managing functions
    // ...

  private:
    char *mData;
    size_t mSize;
};

In some scenarios, we could pass the control over the buffer from an entity to another. for instance, we have a buffer builder class that constructs the buffer then pass it to the caller entity.

class BufferBuilder {
  public:
    static Buffer get(size_t size) {
        Buffer _buf(size);
        return _buf;
    }
};

constexpr size_t bufferSize = 1000;
void foo() {
    auto buffer = BufferBuilder::get(bufferSize);
}

By calling foo(), the system creates the buffer by allocation from the heap a chunk with bufferSize size in the BufferBuilder class. Then passes it back to the caller entity using the copy constructor, which allocated another chunk of memory with bufferSize size, and copies the contents of the original buffer. This seems like a redundant operation, why would the system allocate the buffer twice for one single buffer?. In C++11, we can avoid this redundant allocation by utilizing the move feature.

Move contructors and move assignments

In C++11, the rule of three became the rule of five adding move constructor and move assignment. For a class T, the move constructor and assignment are as follows:

T(T&&); // Move constructor
T& operator=(T&&); // Move assignment

In our Buffer class, we add them as follows:

// Move constructor
Buffer(Buffer&& rhs) {
    mData = std::move(rhs.mData); // std::move ??
    rhs.mData = nullptr;
    mSize = rhs.mSize;
}
// Move assignments
Buffer& operator=(Buffer&& rhs) {
    mData = std::move(rhs.mData);
    rhs.mData = nullptr;
    mSize = rhs.mSize;
    return *this;
}

By adding these constructors, the compiler (tested on MSVC142) will choose to move the _buf object created in the BufferBuilder::get instead of copying it.

R-value references (&&)

R-value references are newly introduced to C++11. R-value references are types that refer to an r-value (inferred from the name :D). It’s pretty cool to have a variable that binds to a temporary object (r-value) by reference, isn’t it?

int&& i; // r-value reference
int& i; // r-value/lvalue-reference
int i; // contains lvalue

std::move() ?

First, lets have a look at std::move definition in the standard.

template< class T >
constexpr std::remove_reference_t<T>&& move( T&& t ) noexcept {
    return static_cast<typename std::remove_reference<T>::type&&>(t)
}

std::move simply casts the object to an r-value reference (a reference to a temporary object), which means lvalues and lvalue-references can bind to them and take control over the data.

Be careful: trying to access a variable after using std::move on it is undefined behaviour :(.

Using r-value references to create better function overloads

Assume a scenario where we are writing a utility function that accepts Buffer and pass it to another function.

void acceptBuffer(const Buffer& s_) {
    sendBuffer(s_);
}

void acceptBuffer(Buffer&& s_) {
    sendBuffer(std::move(s_));
}

We can implement two function overloads as above. If we are passing r-value to acceptBuffer, the second overload will accept it and moves it to sendBuffer. And if we are passing an lvalue, the first overload will accept it, and pass a copy to sendBuffer.

Movable objects and STL containers

STL containers such as std::vector, std::list and std::map are move-aware containers. This means you can move an object by calling move aware functions (e.g. emplace_back). There’s one limitation to that: move constructor and move assignment operator have to be noexcept to be used by the move-aware containers in STL.