Sitemap
Salarmanan

Salarmanan

Move Semantics — C++

5 min readOct 26, 2025

C++11 introduced move semantics which allow us to reduce unnecessary copies and improve performance, especially when dealing with large objects. However, they are a double edged sword as they can be misunderstood and misused easily, leading to unnecessary complexity and subtle bugs. This article will give you a basic understanding of them so you can use them effectively in your code.

Even though C++17 introduced copy elision which optimizes a lot of unnecessary copies out (such as the one we will be covering in the main example of the article), it is still very valuable and important to be aware and efficient with move semantics as a C++ developer.

Before diving into move semantics, you should be comfortable with the following concepts:

  • References and pointers
  • Rvalues and lvalues
  • The copy constructor

Let’s first introduce a simple example. Imagine you are programming a video game that needs its own string class for player names. Such a class might look like this:

class String {
public:
String(const char* data)
: m_Size(strlen(data))
, m_Data(new char[m_Size]) {
memcpy(m_Data, data, m_Size);
std::cout << "Created!" << std::endl;
}

~String() {
std::cout << "Destroyed!" << std::endl;
delete[] m_Data;
}

String(const String& other)
: m_Size(other.m_Size)
, m_Data(new char[m_Size]) {
memcpy(m_Data, other.m_Data, m_Size);
std::cout << "Copied!" << std::endl;
}

void Print() const {
for (int i {}; i < m_Size; i++) {
std::cout << m_Data[i];
}
std::cout << std::endl;
}

private:
unsigned m_Size {};
char* m_Data {};
};

This simple class is a wrapper around a character buffer. One important detail of this class that you should notice is that we print out “Copied!” or “Created!” or “Destroyed!” whenever an instance of the class is copied or created respectively. We will utilize this class for storing player names for example. Talking about players, let’s introduce the player class:

class Player {
public:
Player(const String& name)
: m_Name(name) {
}

void Talk() const {
std::cout << "Hello my name is ";
m_Name.Print();
}

private:
String m_Name;
};

Let’s actually utilize these classes in our main method:

int main() {
Player player { String { "Joe" } };
player.Talk();
return 0;
}

As you can see we simply create a temporary instance of the string class in our main method and immediately pass it into the player constructor so that we can properly initialize our player’s name. Afterwards our player talks and introduces himself.

Now I’ll be giving you a bit of time to think what the output might be. Remember that created and copied are printed whenever they occur in the string class.

The output will be the following:

Created!
Copied!
Destroyed!
Hello my name is Joe
Destroyed!

Why is this bad? We only really care about the first two print statements. We first create the temporary string object.

This string object is created directly in main, which means it allocates its own buffer on the heap. When we pass it to the player constructor, the copy constructor is invoked, causing a second heap allocation and another full copy of the character data. After the player object is initialized, the temporary string is immediately destroyed, making that initial allocation and copy completely pointless. We paid the full cost of duplication for an object that only lived long enough to be thrown away.

Thus the question arises of how we can remove this unnecessary duplication. Let me introduce you to moving objects!

Get Salarmanan’s stories in your inbox

Join Medium for free to get updates from this writer.

To construct the string class within the main function is inevitable. We will need to create the string instance for the player name somewhere and that has to be done at least once so we do not have to change much.

However the copy we do is rather unnecessary. Because the value we pass in is simply an rvalue reference and will (likely) be immediately thrown away, instead of copying we can “steal” its resources. We can only do that because it is an rvalue. This tells us that the object is not necessary for the program’s execution flow but rather is only used temporarily for initialization purposes.

So first let’s define an overload for the player constructor which takes in an rvalue reference.

 Player(String&& tempStr)
: m_Name(static_cast<String&&>(tempStr)) {
}

Because this rvalue reference is bound to the variable tempStr it immediately becomes an lvalue reference when we try to forward it into the constructor of string. That is why we manually cast it into an rvalue reference. But casting it into an rvalue reference comes with some implications, that go beyond the scope of this article. That is why you should rather use “std::move” like this:

 Player(String&& tempStr)
: m_Name(std::move(tempStr)) {
}

Now we have to create a so-called move constructor inside our string class to handle this case where we want to steal the data of that temporary object.

 String(String&& other) noexcept
: m_Size(other.m_Size)
, m_Data(other.m_Data) {
std::cout << "Moved!" << std::endl;

other.m_Size = 0;
other.m_Data = nullptr;
}

This is the move constructor of the string class. What it does is very simple. It steals the resources in the temporary rvalue object which shall be moved to the destination (this) by simply setting the members such as m_Data and m_Size to the ones of other. So no heap allocation is made. However because other is likely to be destroyed afterwards, it sets its members to their default 0 values. Now “other” is a so-called “hollow object” and when its destructor is called the instruction“delete[] nullptr” is executed which does nothing at all. If we would not have done that, “other” could be deleted and with it the memory to the character buffer would be destroyed.

After our changes, when the main method is executed, instead of having a copy, the new output would look something like this:

Created!
Moved!
Destroyed!
Hello my name is Joe
Destroyed!

So there is no unnecessary heap allocation being made and we are happy because we saved 0.001 ms.

Obviously in this example it does not really matter if we do this copy or not, however, you can already see how move semantics can pose a great benefit for optimization purposes on a larger scale.

I have only introduced you to the basics but you could ask yourself these questions if you would like to go deeper:

  1. Why should we use std::move(v) instead of static_cast<T&&>(v)?
  2. Why do we mark move constructors as noexcept?
  3. What is the rule of three and the rule of five?
  4. What happens if I move from a const object?
  5. What is the state of an object after it has been moved from, and what am I still allowed to do with it?
  6. What happens if a move constructor throws an exception?
  7. Can I move an object more than once and should I?

--

--