std::move – Efficient Resource Transfer from One Object to Another

C++11 introduced this std::move() to enable efficient resource transfer from one object to another. Let’s first understand what efficient resource transfer means.

#include <iostream>
using namespace std;

int main() {
    
    string s1("QnA Plus");
    string s2 = s1;

    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}
$ g++ -o test test.cpp
$ ./test 
First string: QnA Plus
Second string: QnA Plus

We created a string object, s1, with value, ‘QnA Plus‘. We can imagine what might have happened during this s1 object creation. One constructor would have been called that allocated memory how hold the value ‘QnA Plus‘.

In the next statement, we assigned s1 to another string object s2. Without knowing the std::string implementation, we can imagine that a constructor of std::string would have been called. Memory would be allocated to hold the value. There would be memory copy involved also to get the value, ‘QnA Plus‘, from s1 to s2.

From the output, we can see that both the objects hold the same value. But in some situations, we don’t care whether the source object retains its value (and other resources) or not. And in other situations, like std::unique_ptr, we don’t want more than one objects holding same resource.

That means that in some situations, we want to transfer, not copy, resources from one object to another. But in a very efficient way. We don’t want the usual memory allocations and copies required in resource transfer. After the transfer, the source object will no longer hold the resource.

A typical ‘swap two strings‘ code looks like this.

// a and b are of type std::string
std::string tmp = a;
a = b;
b = tmp;

In the first statement, ‘a‘ is assigned to a temporary object ‘tmp‘. Here we don’t care whether ‘a‘ retains its value (resource) or not because it will be overwritten in the next statement anyway. But here the memory allocation and copy will be involved like we discussed earlier.

How can we transfer the value without memory allocation and copy?

std::move, the Solution

That’s where the std::move comes into the picture to transfer resources without doing extra memory allocation and copy.

#include <iostream>
using namespace std;

int main() {
    
    string s1("QnA Plus");
    string s2 = move(s1);

    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}
$ ./test 
First string: 
Second string: QnA Plus

Notice this line: string s2 = move(s1);. Instead of copying s1 to s2, we moved the resources from s1 to s2. Here the additional memory allocation and copy will not be involved to transfer the resource. The source object s1 will not also retain its resource after the move. From the output, we can see that the value does not exist in s1.

Remember that the std::move() does not actually move an object. It casts the input object to a rvalue reference.

std::move(t) is equivalent to:

static_cast<typename std::remove_reference<T>::type&&>(t)

This triggers the invoke of the move constructor of the object that does the actual move operation.

All objects does not automatically become movable. The std::string class has a move constructor, that’s why string object move is possible.

Making Custom Class Movable

#include <iostream>
#include <cstring>

using namespace std;

class mystring {
public:
    mystring(const char * s) {
        if (s == nullptr) {
            s = nullptr;
            return;
        }
        str = new char[strlen(s)];
        strcpy(str, s);
    }

    mystring(mystring &rhs) {
        if (rhs.str == nullptr) {
            str = nullptr;
            return;
        }

        str = new char[strlen(rhs.str)];
        strcpy(str, rhs.str);
    }

    friend ostream& operator<<(ostream& os, const mystring& s) {
        if (s.str == nullptr) return os;
        os << s.str;
        return os;
    }
private:
    char *str;
};

int main() {
    
    mystring s1("QnA Plus");
    mystring s2 = s1;
    // mystring s2 = move(s1); /*This will produce compilation error.*/

    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}
$ ./test 
First string: QnA Plus
Second string: QnA Plus

We implemented a very basic string class, myclass, with a copy constructor but no move constructor.

We can see that we can assign one object to another. The source object retains its resource.

If we replace mystring s2 = s1; with mystring s2 = move(s1);, we’ll get a compilation error like this.

$ g++ -o test test.cpp 
test.cpp: In function ‘int main()’:
test.cpp:38:23: error: cannot bind non-const lvalue reference of type ‘mystring&’ to an rvalue of type ‘std::remove_reference<mystring&>::type’ {aka ‘mystring’}
   38 |     mystring s2 = move(s1);
      |                   ~~~~^~~~
test.cpp:17:24: note:   initializing argument 1 of ‘mystring::mystring(mystring&)’
   17 |     mystring(mystring &rhs) {
      |              ~~~~~~~~~~^~~

If we add a move constructor to the mystring class, the mystring s2 = move(s1); statement will start working.

#include <iostream>
#include <cstring>

using namespace std;

class mystring {
public:
    mystring(const char * s) {
        if (s == nullptr) {
            s = nullptr;
            return;
        }
        str = new char[strlen(s)];
        strcpy(str, s);
    }

    mystring(mystring &rhs) {
        if (rhs.str == nullptr) {
            str = nullptr;
            return;
        }

        str = new char[strlen(rhs.str)];
        strcpy(str, rhs.str);
    }

    /*The move constuctor*/
    mystring(mystring&& s) {
        str = s.str;
        s.str = nullptr;
    }

    friend ostream& operator<<(ostream& os, const mystring& s) {
        if (s.str == nullptr) return os;
        os << s.str;
        return os;
    }
private:
    char *str;
};

int main() {
    
    mystring s1("QnA Plus");
    mystring s2 = move(s1);
    
    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}
$ ./test 
First string: 
Second string: QnA Plus

Unlike the copy constructor, notice that the move constructor does not have memomry allocation or copy.

To make a class movable, we need to add a move assignment operator also. It is not done here.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0