From C++11, in addition to a copy constructor, we can have another special constructor called move constructor. The move constructor makes a class movable that enables efficient resource transfer.
Let’s first quickly recap what a copy constructor does.
#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);
}
/* Copy Constructor */
mystring(const mystring &rhs) {
if (rhs.str == nullptr) {
str = nullptr;
return;
}
str = new char[strlen(rhs.str)];
strcpy(str, rhs.str);
cout << "Copy Constructor called." << endl;
}
void change(const char * s) {
if (s == nullptr || str == nullptr) return; /*Better handling possible. Not done for simplicity.*/
strncpy(str, s, strlen(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;
cout << "First string: " << s1 << endl;
cout << "Second string: " << s2 << endl;
return 0;
}
$ g++ -o test test.cpp
$ ./test
Copy Constructor called.
First string: QnA Plus
Second string: QnA Plus
We have a copy constructor in our mystring class. We created an object s1 with value “QnA Plus“. Then we created another object s2 initialized from s1.
In time of s2 initialization, the copy constructor got called. The copy constructor allocated memory to hold the new value and copied the value from the source object s1.
The copy constructor did exactly what should have been done!
Both the objects will have their own copy of resources (values). And modifying one object will not interfere with other. Here the source object, s1, remains intact after the s2 initialization.
But in some situations, we don’t care whether the source object remains intact or not.
A typical ‘swap two strings‘ code looks like this.
// a and b are of type mystring
mystring tmp = a;
a = b;
b = tmp;
Here we don’t care whether ‘a‘ retains its value (resource) or not because it will be overwritten in the next statement anyway. In this case we can avoid the memory allocation and copy that the copy constructor has.
Modifying the copy constructor is not a good idea at all because in other situation we need the source object to remain intact.
The Move Constructor
The move constructor can serve our purpose. It can transfer the resources from the source object to the destination without additional memory allocation or copy. This mechanism is called efficient resource transfer.
#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);
}
/* Copy Constructor */
mystring(const mystring &rhs) {
if (rhs.str == nullptr) {
str = nullptr;
return;
}
str = new char[strlen(rhs.str)];
strcpy(str, rhs.str);
cout << "Copy Constructor called." << endl;
}
/* Move Constuctor */
mystring(mystring&& s) {
str = s.str;
s.str = nullptr;
cout << "Move Constructor called." << endl;
}
void change(const char * s) {
if (s == nullptr || str == nullptr) return; /*Better handling possible. Not done for simplicity.*/
strncpy(str, s, strlen(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 = static_cast<mystring&&>(s1);
cout << "First string: " << s1 << endl;
cout << "Second string: " << s2 << endl;
return 0;
}
$ ./test
Move Constructor called.
First string:
Second string: QnA Plus
We added a move constructor to the existing class, mystring. To call the move constructor during object initialization, we need to initialize it from a rvalue reference of an object. That’s why we cast-ed the s1 to a rvalue reference – static_cast<mystring&&>(s1).
The move constructor does not have any memory allocation or copy. So, it is very efficient way to transfer resources from one object to another compare to a copy constructor.
Please note that the stastic_cast, that we used here, might not work in all conditions, especially, if you work with template type references. It’s better to use C++ standard library provided std::move for this purpose.
So the mystring s2 = static_cast<mystring&&>(s1); line should look like mystring s2 = move(s1); .
Move constructors look very similar to copy constructors. They take rvalue reference (&&) type input parameter instead of lvalue reference (&).
We should avoid memory operations as much as possible considering that the source object will be invalidated. We should assign nullptr to the pointer members of the source object to avoid accidental malicious access.
To make a class movable we should have both move constructor and move assignment operator.