Move constructors and move assignment operators work with rvalues. What if you want to use them with lvalues? For instance, a program could analyze an array of some sort of candidate objects, select one object for further use, and discard the array. It would be convenient if you could use a move constructor or a move assignment operator to preserve the selected object. However, suppose you tried the following:
Useless choices[10];
Useless best;
int pick;
... // select one object, set pick to index
best = choices[pick];
The choices[pick]
object is an lvalue, so the assignment statement will use the copy assignment operator, not the move assignment operator. But if you could make choices[pick]
look like an rvalue, then the move assignment operator would be used. This can be done by using the static_cast<>
operator to cast the object to type Useless &&
. C++11 provides a simpler way to do this—use the std::move()
function, which is declared in the utility
header file. Listing 18.3 illustrates this technique. It adds verbose versions of the assignment operators to the Useless
class while silencing the previously verbose constructors and destructor.
// stdmove.cpp -- using std::move()
#include <iostream>
#include <utility>
// interface
class Useless
{
private:
int n; // number of elements
char * pc; // pointer to data
static int ct; // number of objects
void ShowObject() const;
public:
Useless();
explicit Useless(int k);
Useless(int k, char ch);
Useless(const Useless & f); // regular copy constructor
Useless(Useless && f); // move constructor
~Useless();
Useless operator+(const Useless & f)const;
Useless & operator=(const Useless & f); // copy assignment
Useless & operator=(Useless && f); // move assignment
void ShowData() const;
};
// implementation
int Useless::ct = 0;
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
}
Useless::Useless(int k) : n(k)
{
++ct;
pc = new char[n];
}
Useless::Useless(int k, char ch) : n(k)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = ch;
}
Useless::Useless(const Useless & f): n(f.n)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
Useless::Useless(Useless && f): n(f.n)
{
++ct;
pc = f.pc; // steal address
f.pc = nullptr; // give old object nothing in return
f.n = 0;
}
Useless::~Useless()
{
delete [] pc;
}
Useless & Useless::operator=(const Useless & f) // copy assignment
{
std::cout << "copy assignment operator called:
";
if (this == &f)
return *this;
delete [] pc;
n = f.n;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
return *this;
}
Useless & Useless::operator=(Useless && f) // move assignment
{
std::cout << "move assignment operator called:
";
if (this == &f)
return *this;
delete [] pc;
n = f.n;
pc = f.pc;
f.n = 0;
f.pc = nullptr;
return *this;
}
Useless Useless::operator+(const Useless & f)const
{
Useless temp = Useless(n + f.n);
for (int i = 0; i < n; i++)
temp.pc[i] = pc[i];
for (int i = n; i < temp.n; i++)
temp.pc[i] = f.pc[i - n];
return temp;
}
void Useless::ShowObject() const
{
std::cout << "Number of elements: " << n;
std::cout << " Data address: " << (void *) pc << std::endl;
}
void Useless::ShowData() const
{
if (n == 0)
std::cout << "(object empty)";
else
for (int i = 0; i < n; i++)
std::cout << pc[i];
std::cout << std::endl;
}
// application
int main()
{
using std::cout;
{
Useless one(10, 'x'),
Useless two = one +one; // calls move constructor
cout << "object one: ";
one.ShowData();
cout << "object two: ";
two.ShowData();
Useless three, four;
cout << "three = one
";
three = one; // automatic copy assignment
cout << "now object three = ";
three.ShowData();
cout << "and object one = ";
one.ShowData();
cout << "four = one + two
";
four = one + two; // automatic move assignment
cout << "now object four = ";
four.ShowData();
cout << "four = move(one)
";
four = std::move(one); // forced move assignment
cout << "now object four = ";
four.ShowData();
cout << "and object one = ";
one.ShowData();
}
}
object one: xxxxxxxxxx
object two: xxxxxxxxxxxxxxxxxxxx
three = one
copy assignment operator called:
now object three = xxxxxxxxxx
and object one = xxxxxxxxxx
four = one + two
move assignment operator called:
now object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
four = move(one)
move assignment operator called:
now object four = xxxxxxxxxx
and object one = (object empty)
As you can see, assigning one
to three
invokes copy assignment, but assigning move(one)
to four
invokes move assignment.
You should realize that the std::move()
function doesn’t necessarily produce a move operation. Suppose, for instance, that Chunk
is a class with private data and that we have the following code:
Chunk one;
...
Chunk two;
two = std::move(one); // move semantics?
The expression std::move(one)
is an rvalue, so the assignment statement will invoke the move assignment operator for Chunk
, providing that one has been defined. But if the Chunk
class doesn’t define a move assignment operator, the compiler will use the copy assignment operator. And if that also isn’t defined, then assignment isn’t allowed at all.
The main benefit rvalue references bring to most programmers is not the opportunity to write code using them. Rather, it is the opportunity to use library code that utilizes rvalue references to implement move semantics. For example, the STL classes now have copy constructors, move constructors, copy assignment operators, and move assignment operators.
C++11 adds several features to classes in addition to those already mentioned in this chapter—that is, explicit conversion operators and in-class member initialization.
18.118.2.225