‘What is std::move(), and when should it be used?

Published:

Problem

Here I’m back, on my series of C++. Today we’re going to be talking about the std::move semantics. This is probably one of the most requested topic. In this article, I would like to make a introduction, because the basic of std::move is very simple, but then what you can do with it… it could be very complex.

Before to continue this article, I highly recomend that read the my article about the lvalues and rvalues from HERE, because this article more or less going to be a continuation of that article.

The Basics

Move semantics essentially just allow us to move objects around. This wasn’t possible before C++11. The C++11 introduced the rvalue references, which are necessery for the move semantics. The basic idea is when we are writing a C++ code, there are lot of cases in which we don’t really need to or want to necessarily copy an object one place from the other.

For example:

  • If passing an object into a function that then is going to take the ownership of that object, i have no choice to move it. I need to copy

  • The same things occur when I want to return an object from a function. I still have to create that object inside that actual function and then return it. Which means that I’m coping that data again.

Yes… I know…Now many reader says..

Let’s write the example why exactly we might want to move something.

class String
{
public:
	String() = default;

	String(const char* aStr)
	{
		printf("CONSTRUCTOR!\n");
		mSize = strlen(aStr);
		mData = new char[mSize];
		memcpy(mData, aStr, mSize);
	}

	~String()
	{
		printf("DESTRUCTOR!\n");
		delete mData;
	}


	String(const String& aOther)
	{
		printf("COPY CONSTRUCTOR!\n");
		mSize = aOther.mSize;
		mData = new char[mSize];
		memcpy(mData, aOther.mData, aOther.mSize);
	}


	void Print()
	{

		for(uint32_t i = 0; i < mSize; ++i)
  			printf("%c", mData[i]);
		printf("\n");
	}

private:
	char*    mData;
	uint32_t mSize;

};


string Entity
{
public:
	Entity(const String& aName)
		: mName(aName)
	{


	}

	void Print()
	{
		mName.Print();
	}
private:
	String mName;
}


int main()
{

	Entity lEntity( String("MyEntity") );
	lEntity.Print();
}

It look all fine? Right? Let’s execute our code.

>> CONSTRUCTOR
>> COPY CONSTRUCTOR
>> MyEntity
>> DESTRUCTOR

Where is the problem? The answer is at the COPY CONSTRUCTOR line print. This means that our data was copied. Why it is a problem? It is a problem, because when we copy the string, we need to allocate the memory on the heap with mData = new char[mSize];. Say with the other words, when we excecute the Entity lEntity( String(“MyEntity”) ); we are allocate the memory TWICE. Create first of the scope of ” String(“MyEntity”)” and then the secondly at “Entity(const String& aName): mName(aName)” we are copy the aName to the mName.

So the question why can’t we just allocate the memory and then move it onto the other place? What we need to do?

The std::move

class String
{
public:
	String() = default;

	String(const char* aStr)
	{
		printf("CONSTRUCTOR!\n");
		mSize = strlen(aStr);
		mData = new char[mSize];
		memcpy(mData, aStr, mSize);
	}

	~String()
	{
		printf("DESTRUCTOR!\n");
		delete mData;
	}

	//Move contructor
	String(String&& aOther) noexcept
	{
		printf("MOVE CONSTRUCTOR!\n");
		mSize = aOther.mSize;
		mData = aOther.mData;

		aOther.mData = nullptr;
		aOther.mSize = 0;
	}


	void Print()
	{

		for(uint32_t i = 0; i < mSize; ++i)
  			printf("%c", mData[i]);
		printf("\n");
	}

private:
	char*    mData;
	uint32_t mSize;

};


string Entity
{
public:
	Entity(String&& aName)
		: mName(std::move( aName ) )
	{


	}

	void Print()
	{
		mName.Print();
	}
private:
	String mName;
}


int main()
{

	Entity lEntity( String("MyEntity") );
	lEntity.Print();
}

And run the above code, we got the following output:

>> CONSTRUCTOR
>> MOVE CONSTRUCTOR
>> MyEntity
>> DESTRUCTOR

It cool? Right?

The major point of this is that we saved using the copy constructor to allocate the new block of memory and copy it. We are simply manage to move that instead. We have only the single allocation.