13.6. Moving Objects
AdvancedOne of the major features in the new standard is the ability to move rather than copy an object. As we saw in § 13.1.1 (p. 497), copies are made in many circumstances. In some of these circumstances, an object is immediately destroyed after it is copied. In those cases, moving, rather than copying, the object can provide a significant performance boost.
As we’ve just seen, our StrVec
class is a good example of this kind of superfluous copy. During reallocation, there is no need to copy—rather than move—the elements from the old memory to the new. A second reason to move rather than copy occurs in classes such as the IO or unique_ptr
classes. These classes have a resource (such as a pointer or an IO buffer) that may not be shared. Hence, objects of these types can’t be copied but can be moved.
Under earlier versions of the language, there was no direct way to move an object. We had to make a copy even if there was no need for the copy. If the objects are large, or if the objects themselves require memory allocation (e.g., string
s), making a needless copy can be expensive. Similarly, in previous versions of the library, classes stored in a container had to be copyable. Under the new standard, we can use containers on types that cannot be copied so long as they can be moved.
INFO
The library containers, string
, and shared_ptr
classes support move as well as copy. The IO and unique_ptr
classes can be moved but not copied.
13.6.1. Rvalue References
AdvancedC++11To support move operations, the new standard introduced a new kind of reference, an rvalue reference. An rvalue reference is a reference that must be bound to an rvalue. An rvalue reference is obtained by using &&
rather than &
. As we’ll see, rvalue references have the important property that they may be bound only to an object that is about to be destroyed. As a result, we are free to “move” resources from an rvalue reference to another object.
Recall that lvalue and rvalue are properties of an expression (§ 4.1.1, p. 135). Some expressions yield or require lvalues; others yield or require rvalues. Generally speaking, an lvalue expression refers to an object’s identity whereas an rvalue expression refers to an object’s value.
Like any reference, an rvalue reference is just another name for an object. As we know, we cannot bind regular references—which we’ll refer to as lvalue references when we need to distinguish them from rvalue references—to expressions that require a conversion, to literals, or to expressions that return an rvalue (§ 2.3.1, p. 51). Rvalue references have the opposite binding properties: We can bind an rvalue reference to these kinds of expressions, but we cannot directly bind an rvalue reference to an lvalue:
int i = 42;
int &r = i; // ok: r refers to i
int &&rr = i; // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42; // error: i * 42 is an rvalue
const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i * 42; // ok: bind rr2 to the result of the multiplication
Functions that return lvalue references, along with the assignment, subscript, dereference, and prefix increment/decrement operators, are all examples of expressions that return lvalues. We can bind an lvalue reference to the result of any of these expressions.
Functions that return a nonreference type, along with the arithmetic, relational, bitwise, and postfix increment/decrement operators, all yield rvalues. We cannot bind an lvalue reference to these expressions, but we can bind either an lvalue reference to const
or an rvalue reference to such expressions.
Lvalues Persist; Rvalues Are Ephemeral
Looking at the list of lvalue and rvalue expressions, it should be clear that lvalues and rvalues differ from each other in an important manner: Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions.
Because rvalue references can only be bound to temporaries, we know that
- The referred-to object is about to be destroyed
- There can be no other users of that object
These facts together mean that code that uses an rvalue reference is free to take over resources from the object to which the reference refers.
INFO
Rvalue references refer to objects that are about to be destroyed. Hence, we can “steal” state from an object bound to an rvalue reference.
Variables Are Lvalues
Although we rarely think about it this way, a variable is an expression with one operand and no operator. Like any other expression, a variable expression has the lvalue/rvalue property. Variable expressions are lvalues. It may be surprising, but as a consequence, we cannot bind an rvalue reference to a variable defined as an rvalue reference type:
int &&rr1 = 42; // ok: literals are rvalues
int &&rr2 = rr1; // error: the expression rr1 is an lvalue!
Given our previous observation that rvalues represent ephemeral objects, it should not be surprising that a variable is an lvalue. After all, a variable persists until it goes out of scope.
INFO
A variable is an lvalue; we cannot directly bind an rvalue reference to a variable even if that variable was defined as an rvalue reference type.
The Library move
Function
C++11Although we cannot directly bind an rvalue reference to an lvalue, we can explicitly cast an lvalue to its corresponding rvalue reference type. We can also obtain an rvalue reference bound to an lvalue by calling a new library function named move
, which is defined in the utility
header. The move
function uses facilities that we’ll describe in § 16.2.6 (p. 690) to return an rvalue reference to its given object.
int &&rr3 = std::move(rr1); // ok
Calling move
tells the compiler that we have an lvalue that we want to treat as if it were an rvalue. It is essential to realize that the call to move
promises that we do not intend to use rr1
again except to assign to it or to destroy it. After a call to move
, we cannot make any assumptions about the value of the moved-from object.
INFO
We can destroy a moved-from object and can assign a new value to it, but we cannot use the value of a moved-from object.
As we’ve seen, differently from how we use most names from the library, we do not provide a using
declaration (§ 3.1, p. 82) for move
(§ 13.5, p. 530). We call std::move
not move
. We’ll explain the reasons for this usage in § 18.2.3 (p. 798).
WARNING
Code that uses move
should use std::move
, not move
. Doing so avoids potential name collisions.
INFO
Exercises Section 13.6.1
Exercise 13.45: Distinguish between an rvalue reference and an lvalue reference.
Exercise 13.46: Which kind of reference can be bound to the following initializers?
int f();
vector<int> vi(100);
int? r1 = f();
int? r2 = vi[0];
int? r3 = r1;
int? r4 = vi[0] * f();
Exercise 13.47: Give the copy constructor and copy-assignment operator in your String
class from exercise 13.44 in § 13.5 (p. 531) a statement that prints a message each time the function is executed.
Exercise 13.48: Define a vector<String>
and call push_back
several times on that vector
. Run your program and see how often String
s are copied.
13.6.2. Move Constructor and Move Assignment
AdvancedLike the string
class (and other library classes), our own classes can benefit from being able to be moved as well as copied. To enable move operations for our own types, we define a move constructor and a move-assignment operator. These members are similar to the corresponding copy operations, but they “steal” resources from their given object rather than copy them.
Like the copy constructor, the move constructor has an initial parameter that is a reference to the class type. Differently from the copy constructor, the reference parameter in the move constructor is an rvalue reference. As in the copy constructor, any additional parameters must all have default arguments.
In addition to moving resources, the move constructor must ensure that the moved-from object is left in a state such that destroying that object will be harmless. In particular, once its resources are moved, the original object must no longer point to those moved resources—responsibility for those resources has been assumed by the newly created object.
As an example, we’ll define the StrVec
move constructor to move rather than copy the elements from one StrVec
to another:
StrVec::StrVec(StrVec &&s) noexcept // move won't throw any exceptions
// member initializers take over the resources in s
: elements(s.elements), first_free(s.first_free), cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor
s.elements = s.first_free = s.cap = nullptr;
}
We’ll explain the use of noexcept
(which signals that our constructor does not throw any exceptions) shortly, but let’s first look at what this constructor does.
Unlike the copy constructor, the move constructor does not allocate any new memory; it takes over the memory in the given StrVec
. Having taken over the memory from its argument, the constructor body sets the pointers in the given object to nullptr
. After an object is moved from, that object continues to exist. Eventually, the moved-from object will be destroyed, meaning that the destructor will be run on that object. The StrVec
destructor calls deallocate
on first_free
. If we neglected to change s.first_free
, then destroying the moved-from object would delete the memory we just moved.
Move Operations, Library Containers, and Exceptions
TrickyBecause a move operation executes by “stealing” resources, it ordinarily does not itself allocate any resources. As a result, move operations ordinarily will not throw any exceptions. When we write a move operation that cannot throw, we should inform the library of that fact. As we’ll see, unless the library knows that our move constructor won’t throw, it will do extra work to cater to the possibliity that moving an object of our class type might throw.
C++11One way inform the library is to specify noexcept
on our constructor. We’ll cover noexcept
, which was introduced by the new standard, in more detail in § 18.1.4 (p. 779). For now what’s important to know is that noexcept
is a way for us to promise that a function does not throw any exceptions. We specify noexcept
on a function after its parameter list. In a constructor, noexcept
appears between the parameter list and the :
that begins the constructor initializer list:
class StrVec {
public:
StrVec(StrVec&&) noexcept; // move constructor
// other members as before
};
StrVec::StrVec(StrVec &&s) noexcept : /* member initializers */
{ /* constructor body */ }
We must specify noexcept
on both the declaration in the class header and on the definition if that definition appears outside the class.
INFO
Move constructors and move assignment operators that cannot throw exceptions should be marked as noexcept
.
Understanding why noexcept
is needed can help deepen our understanding of how the library interacts with objects of the types we write. We need to indicate that a move operation doesn’t throw because of two interrelated facts: First, although move operations usually don’t throw exceptions, they are permitted to do so. Second, the library containers provide guarantees as to what they do if an exception happens. As one example, vector
guarantees that if an exception happens when we call push_back
, the vector
itself will be left unchanged.
Now let’s think about what happens inside push_back
. Like the corresponding StrVec
operation (§ 13.5, p. 527), push_back
on a vector
might require that the vector
be reallocated. When a vector
is reallocated, it moves the elements from its old space to new memory, just as we did in reallocate
(§ 13.5, p. 530).
As we’ve just seen, moving an object generally changes the value of the moved-from object. If reallocation uses a move constructor and that constructor throws an exception after moving some but not all of the elements, there would be a problem. The moved-from elements in the old space would have been changed, and the unconstructed elements in the new space would not yet exist. In this case, vector
would be unable to meet its requirement that the vector
is left unchanged.
On the other hand, if vector
uses the copy constructor and an exception happens, it can easily meet this requirement. In this case, while the elements are being constructed in the new memory, the old elements remain unchanged. If an exception happens, vector
can free the space it allocated (but could not successfully construct) and return. The original vector
elements still exist.
To avoid this potential problem, vector
must use a copy constructor instead of a move constructor during reallocation unless it knows that the element type’s move constructor cannot throw an exception. If we want objects of our type to be moved rather than copied in circumstances such as vector
reallocation, we must explicity tell the library that our move constructor is safe to use. We do so by marking the move constructor (and move-assignment operator) noexcept
.
Move-Assignment Operator
The move-assignment operator does the same work as the destructor and the move constructor. As with the move constructor, if our move-assignment operator won’t throw any exceptions, we should make it noexcept
. Like a copy-assignment operator, a move-assignment operator must guard against self-assignment:
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
// direct test for self-assignment
if (this != &rhs) {
free(); // free existing elements
elements = rhs.elements; // take over resources from rhs
first_free = rhs.first_free;
cap = rhs.cap;
// leave rhs in a destructible state
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
In this case we check directly whether the this
pointer and the address of rhs
are the same. If they are, the right- and left-hand operands refer to the same object and there is no work to do. Otherwise, we free the memory that the left-hand operand had used, and then take over the memory from the given object. As in the move constructor, we set the pointers in rhs
to nullptr
.
It may seem surprising that we bother to check for self-assignment. After all, move assignment requires an rvalue for the right-hand operand. We do the check because that rvalue could be the result of calling move
. As in any other assignment operator, it is crucial that we not free the left-hand resources before using those (possibly same) resources from the right-hand operand.
A Moved-from Object Must Be Destructible
TrickyMoving from an object does not destroy that object: Sometime after the move operation completes, the moved-from object will be destroyed. Therefore, when we write a move operation, we must ensure that the moved-from object is in a state in which the destructor can be run. Our StrVec
move operations meet this requirement by setting the pointer members of the moved-from object to nullptr
.
In addition to leaving the moved-from object in a state that is safe to destroy, move operations must guarantee that the object remains valid. In general, a valid object is one that can safely be given a new value or used in other ways that do not depend on its current value. On the other hand, move operations have no requirements as to the value that remains in the moved-from object. As a result, our programs should never depend on the value of a moved-from object.
For example, when we move from a library string
or container object, we know that the moved-from object remains valid. As a result, we can run operations such as as empty
or size
on moved-from objects. However, we don’t know what result we’ll get. We might expect a moved-from object to be empty, but that is not guaranteed.
Our StrVec
move operations leave the moved-from object in the same state as a default-initialized object. Therefore, all the operations of StrVec
will continue to run the same way as they do for any other default-initialized StrVec
. Other classes, with more complicated internal structure, may behave differently.
WARNING
After a move operation, the “moved-from” object must remain a valid, destructible object but users may make no assumptions about its value.
The Synthesized Move Operations
As it does for the copy constructor and copy-assignment operator, the compiler will synthesize the move constructor and move-assignment operator. However, the conditions under which it synthesizes a move operation are quite different from those in which it synthesizes a copy operation.
Recall that if we do not declare our own copy constructor or copy-assignment operator the compiler always synthesizes these operations (§ 13.1.1, p. 497 and § 13.1.2, p. 500). The copy operations are defined either to memberwise copy or assign the object or they are defined as deleted functions.
Differently from the copy operations, for some classes the compiler does not synthesize the move operations at all. In particular, if a class defines its own copy constructor, copy-assignment operator, or destructor, the move constructor and move-assignment operator are not synthesized. As a result, some classes do not have a move constructor or a move-assignment operator. As we’ll see on page 540, when a class doesn’t have a move operation, the corresponding copy operation is used in place of move through normal function matching.
The compiler will synthesize a move constructor or a move-assignment operator only if the class doesn’t define any of its own copy-control members and if every nonstatic
data member of the class can be moved. The compiler can move members of built-in type. It can also move members of a class type if the member’s class has the corresponding move operation:
// the compiler will synthesize the move operations for X and hasX
struct X {
int i; // built-in types can be moved
std::string s; // string defines its own move operations
};
struct hasX {
X mem; // X has synthesized move operations
};
X x, x2 = std::move(x); // uses the synthesized move constructor
hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor
INFO
The compiler synthesizes the move constructor and move assignment only if a class does not define any of its own copy-control members and only if all the data members can be moved constructed and move assigned, respectively.
Unlike the copy operations, a move operation is never implicitly defined as a deleted function. However, if we explicitly ask the compiler to generate a move operation by using = default
(§ 7.1.4, p. 264), and the compiler is unable to move all the members, then the move operation will be defined as deleted. With one important exception, the rules for when a synthesized move operation is defined as deleted are analogous to those for the copy operations (§ 13.1.6, p. 508):
- Unlike the copy constructor, the move constructor is defined as deleted if the class has a member that defines its own copy constructor but does not also define a move constructor, or if the class has a member that doesn’t define its own copy operations and for which the compiler is unable to synthesize a move constructor. Similarly for move-assignment.
- The move constructor or move-assignment operator is defined as deleted if the class has a member whose own move constructor or move-assignment operator is deleted or inaccessible.
- Like the copy constructor, the move constructor is defined as deleted if the destructor is deleted or inaccessible.
- Like the copy-assignment operator, the move-assignment operator is defined as deleted if the class has a
const
or reference member.
For example, assuming Y
is a class that defines its own copy constructor but does not also define its own move constructor:
// assume Y is a class that defines its own copy constructor but not a move constructor
struct hasY {
hasY() = default;
hasY(hasY&&) = default;
Y mem; // hasY will have a deleted move constructor
};
hasY hy, hy2 = std::move(hy); // error: move constructor is deleted
The compiler can copy objects of type Y
but cannot move them. Class hasY
explicitly requested a move constructor, which the compiler is unable to generate. Hence, hasY
will get a deleted move constructor. Had hasY
omitted the declaration of its move constructor, then the compiler would not synthesize the hasY
move constructor at all. The move operations are not synthesized if they would otherwise be defined as deleted.
There is one final interaction between move operations and the synthesized copy-control members: Whether a class defines its own move operations has an impact on how the copy operations are synthesized. If the class defines either a move constructor and/or a move-assignment operator, then the synthesized copy constructor and copy-assignment operator for that class will be defined as deleted.
INFO
Classes that define a move constructor or move-assignment operator must also define their own copy operations. Otherwise, those members are deleted by default.
Rvalues Are Moved, Lvalues Are Copied ...
When a class has both a move constructor and a copy constructor, the compiler uses ordinary function matching to determine which constructor to use (§ 6.4, p. 233). Similarly for assignment. For example, in our StrVec
class the copy versions take a reference to const StrVec
. As a result, they can be used on any type that can be converted to StrVec
. The move versions take a StrVec&&
and can be used only when the argument is a (nonconst
) rvalue:
StrVec v1, v2;
v1 = v2; // v2 is an lvalue; copy assignment
StrVec getVec(istream &); // getVec returns an rvalue
v2 = getVec(cin); // getVec(cin) is an rvalue; move assignment
In the first assignment, we pass v2
to the assignment operator. The type of v2
is StrVec
and the expression, v2
, is an lvalue. The move version of assignment is not viable (§ 6.6, p. 243), because we cannot implicitly bind an rvalue reference to an lvalue. Hence, this assignment uses the copy-assignment operator.
In the second assignment, we assign from the result of a call to getVec
. That expression is an rvalue. In this case, both assignment operators are viable—we can bind the result of getVec
to either operator’s parameter. Calling the copy-assignment operator requires a conversion to const
, whereas StrVec&&
is an exact match. Hence, the second assignment uses the move-assignment operator.
...But Rvalues Are Copied If There Is No Move Constructor
What if a class has a copy constructor but does not define a move constructor? In this case, the compiler will not synthesize the move constructor, which means the class has a copy constructor but no move constructor. If a class has no move constructor, function matching ensures that objects of that type are copied, even if we attempt to move them by calling move
:
class Foo {
public:
Foo() = default;
Foo(const Foo&); // copy constructor
// other members, but Foo does not define a move constructor
};
Foo x;
Foo y(x); // copy constructor; x is an lvalue
Foo z(std::move(x)); // copy constructor, because there is no move constructor
The call to move(x)
in the initialization of z
returns a Foo&&
bound to x
. The copy constructor for Foo
is viable because we can convert a Foo&&
to a const Foo&
. Thus, the initialization of z
uses the copy constructor for Foo
.
It is worth noting that using the copy constructor in place of a move constructor is almost surely safe (and similarly for the assignment operators). Ordinarily, the copy constructor will meet the requirements of the corresponding move constructor: It will copy the given object and leave that original object in a valid state. Indeed, the copy constructor won’t even change the value of the original object.
INFO
If a class has a usable copy constructor and no move constructor, objects will be “moved” by the copy constructor. Similarly for the copy-assignment operator and move-assignment.
Copy-and-Swap Assignment Operators and Move
The version of our HasPtr
class that defined a copy-and-swap assignment operator (§ 13.3, p. 518) is a good illustration of the interaction between function matching and move operations. If we add a move constructor to this class, it will effectively get a move assignment operator as well:
class HasPtr {
public:
// added move constructor
HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}
// assignment operator is both the move- and copy-assignment operator
HasPtr& operator=(HasPtr rhs)
{ swap(*this, rhs); return *this; }
// other members as in § 13.2.1 (p. 511)
};
In this version of the class, we’ve added a move constructor that takes over the values from its given argument. The constructor body sets the pointer member of the given HasPtr
to zero to ensure that it is safe to destroy the moved-from object. Nothing this function does can throw an exception so we mark it as noexcept
(§ 13.6.2, p. 535).
Now let’s look at the assignment operator. That operator has a nonreference parameter, which means the parameter is copy initialized (§ 13.1.1, p. 497). Depending on the type of the argument, copy initialization uses either the copy constructor or the move constructor; lvalues are copied and rvalues are moved. As a result, this single assignment operator acts as both the copy-assignment and move-assignment operator.
For example, assuming both hp
and hp2
are HasPtr
objects:
hp = hp2; // hp2 is an lvalue; copy constructor used to copy hp2
hp = std::move(hp2); // move constructor moves hp2
In the first assignment, the right-hand operand is an lvalue, so the move constructor is not viable. The copy constructor will be used to initialize rhs
. The copy constructor will allocate a new string
and copy the string
to which hp2
points.
In the second assignment, we invoke std::move
to bind an rvalue reference to hp2
. In this case, both the copy constructor and the move constructor are viable. However, because the argument is an rvalue reference, it is an exact match for the move constructor. The move constructor copies the pointer from hp2
. It does not allocate any memory.
Regardless of whether the copy or move constructor was used, the body of the assignment operator swap
s the state of the two operands. Swapping a HasPtr
exchanges the pointer (and int
) members of the two objects. After the swap, rhs
will hold a pointer to the string
that had been owned by the left-hand side. That string
will be destroyed when rhs
goes out of scope.
INFO
Advice: Updating the Rule of Three
All five copy-control members should be thought of as a unit: Ordinarily, if a class defines any of these operations, it usually should define them all. As we’ve seen, some classes must define the copy constructor, copy-assignment operator, and destructor to work correctly (§ 13.1.4, p. 504). Such classes typically have a resource that the copy members must copy. Ordinarily, copying a resource entails some amount of overhead. Classes that define the move constructor and move-assignment operator can avoid this overhead in those circumstances where a copy isn’t necessary.
Move Operations for the Message
Class
Classes that define their own copy constructor and copy-assignment operator generally also benefit by defining the move operations. For example, our Message
and Folder
classes (§ 13.4, p. 519) should define move operations. By defining move operations, the Message
class can use the string
and set
move operations to avoid the overhead of copying the contents
and folders
members.
However, in addition to moving the folders
member, we must also update each Folder
that points to the original Message
. We must remove pointers to the old Message
and add a pointer to the new one.
Both the move constructor and move-assignment operator need to update the Folder
pointers, so we’ll start by defining an operation to do this common work:
// move the Folder pointers from m to this Message
void Message::move_Folders(Message *m)
{
folders = std::move(m->folders); // uses set move assignment
for (auto f : folders) { // for each Folder
f->remMsg(m); // remove the old Message from the Folder
f->addMsg(this); // add this Message to that Folder
}
m->folders.clear(); // ensure that destroying m is harmless
}
This function begins by moving the folders
set. By calling move
, we use the set
move assignment rather than its copy assignment. Had we omitted the call to move
, the code would still work, but the copy is unnecessary. The function then iterates through those Folder
s, removing the pointer to the original Message
and adding a pointer to the new Message
.
It is worth noting that inserting an element to a set
might throw an exception—adding an element to a container requires memory to be allocated, which means that a bad_alloc
exception might be thrown (§ 12.1.2, p. 460). As a result, unlike our HasPtr
and StrVec
move operations, the Message
move constructor and move-assignment operators might throw exceptions. We will not mark them as noexcept
(§ 13.6.2, p. 535).
The function ends by calling clear
on m.folders
. After the move
, we know that m.folders
is valid but have no idea what its contents are. Because the Message
destructor iterates through folders
, we want to be certain that the set
is empty.
The Message
move constructor calls move
to move the contents
and default initializes its folders
member:
Message::Message(Message &&m): contents(std::move(m.contents))
{
move_Folders(&m); // moves folders and updates the Folder pointers
}
In the body of the constructor, we call move_Folders
to remove the pointers to m
and insert pointers to this Message
.
The move-assignment operator does a direct check for self-assignment:
Message& Message::operator=(Message &&rhs)
{
if (this != &rhs) { // direct check for self-assignment
remove_from_Folders();
contents = std::move(rhs.contents); // move assignment
move_Folders(&rhs); // reset the Folders to point to this Message
}
return *this;
}
As with any assignment operator, the move-assignment operator must destroy the old state of the left-hand operand. In this case, destroying the left-hand operand requires that we remove pointers to this Message
from the existing folders
, which we do in the call to remove_from_Folders
. Having removed itself from its Folder
s, we call move
to move the contents
from rhs
to this
object. What remains is to call move_Messages
to update the Folder
pointers.
Move Iterators
The reallocate
member of StrVec
(§ 13.5, p. 530) used a for
loop to call construct
to copy the elements from the old memory to the new. As an alternative to writing that loop, it would be easier if we could call uninitialized_copy
to construct the newly allocated space. However, uninitialized_copy
does what it says: It copies the elements. There is no analogous library function to “move” objects into unconstructed memory.
Instead, the new library defines a move iterator adaptor (§ 10.4, p. 401). A move iterator adapts its given iterator by changing the behavior of the iterator’s dereference operator. Ordinarily, an iterator dereference operator returns an lvalue reference to the element. Unlike other iterators, the dereference operator of a move iterator yields an rvalue reference.
C++11We transform an ordinary iterator to a move iterator by calling the library make_move_iterator
function. This function takes an iterator and returns a move iterator.
All of the original iterator’s other operations work as usual. Because these iterators support normal iterator operations, we can pass a pair of move iterators to an algorithm. In particular, we can pass move iterators to uninitialized_copy
:
void StrVec::reallocate()
{
// allocate space for twice as many elements as the current size
auto newcapacity = size() ? 2 * size() : 1;
auto first = alloc.allocate(newcapacity);
// move the elements
auto last = uninitialized_copy(make_move_iterator(begin()),
make_move_iterator(end()),
first);
free(); // free the old space
elements = first; // update the pointers
first_free = last;
cap = elements + newcapacity;
}
uninitialized_copy
calls construct
on each element in the input sequence to “copy” that element into the destination. That algorithm uses the iterator dereference operator to fetch elements from the input sequence. Because we passed move iterators, the dereference operator yields an rvalue reference, which means construct
will use the move constructor to construct the elements.
It is worth noting that standard library makes no guarantees about which algorithms can be used with move iterators and which cannot. Because moving an object can obliterate the source, you should pass move iterators to algorithms only when you are confident that the algorithm does not access an element after it has assigned to that element or passed that element to a user-defined function.
TIP
Advice: Don’t Be Too Quick to Move
Because a moved-from object has indeterminate state, calling std::move
on an object is a dangerous operation. When we call move
, we must be absolutely certain that there can be no other users of the moved-from object.
Judiciously used inside class code, move
can offer significant performance benefits. Casually used in ordinary user code (as opposed to class implementation code), moving an object is more likely to lead to mysterious and hard-to-find bugs than to any improvement in the performance of the application.
Best Practices
Outside of class implementation code such as move constructors or move-assignment operators, use std::move
only when you are certain that you need to do a move and that the move is guaranteed to be safe.
INFO
Exercises Section 13.6.2
Exercise 13.49: Add a move constructor and move-assignment operator to your StrVec
, String
, and Message
classes.
Exercise 13.50: Put print statements in the move operations in your String
class and rerun the program from exercise 13.48 in § 13.6.1 (p. 534) that used a vector<String>
to see when the copies are avoided.
Exercise 13.51: Although unique_ptr
s cannot be copied, in § 12.1.5 (p. 471) we wrote a clone
function that returned a unique_ptr
by value. Explain why that function is legal and how it works.
Exercise 13.52: Explain in detail what happens in the assignments of the HasPtr
objects on page 541. In particular, describe step by step what happens to values of hp
, hp2
, and of the rhs
parameter in the HasPtr
assignment operator.
Exercise 13.53: As a matter of low-level efficiency, the HasPtr
assignment operator is not ideal. Explain why. Implement a copy-assignment and move-assignment operator for HasPtr
and compare the operations executed in your new move-assignment operator versus the copy-and-swap version.
Exercise 13.54: What would happen if we defined a HasPtr
move-assignment operator but did not change the copy-and-swap operator? Write code to test your answer.
13.6.3. Rvalue References and Member Functions
AdvancedMember functions other than constructors and assignment can benefit from providing both copy and move versions. Such move-enabled members typically use the same parameter pattern as the copy/move constructor and the assignment operators—one version takes an lvalue reference to const
, and the second takes an rvalue reference to nonconst
.
For example, the library containers that define push_back
provide two versions: one that has an rvalue reference parameter and the other a const
lvalue reference. Assuming X
is the element type, these containers define:
void push_back(const X&); // copy: binds to any kind of X
void push_back(X&&); // move: binds only to modifiable rvalues of type X
We can pass any object that can be converted to type X
to the first version of push_back
. This version copies data from its parameter. We can pass only an rvalue that is not const
to the second version. This version is an exact match (and a better match) for nonconst
rvalues and will be run when we pass a modifiable rvalue (§ 13.6.2, p. 539). This version is free to steal resources from its parameter.
Ordinarily, there is no need to define versions of the operation that take a const X&&
or a (plain) X&
. Usually, we pass an rvalue reference when we want to “steal” from the argument. In order to do so, the argument must not be const
. Similarly, copying from an object should not change the object being copied. As a result, there is usually no need to define a version that take a (plain) X&
parameter.
INFO
Overloaded functions that distinguish between moving and copying a parameter typically have one version that takes a const T&
and one that takes a T&&
.
As a more concrete example, we’ll give our StrVec
class a second version of push_back
:
class StrVec {
public:
void push_back(const std::string&); // copy the element
void push_back(std::string&&); // move the element
// other members as before
};
// unchanged from the original version in § 13.5 (p. 527)
void StrVec::push_back(const string& s)
{
chk_n_alloc(); // ensure that there is room for another element
// construct a copy of s in the element to which first_free points
alloc.construct(first_free++, s);
}
void StrVec::push_back(string &&s)
{
chk_n_alloc(); // reallocates the StrVec if necessary
alloc.construct(first_free++, std::move(s));
}
These members are nearly identical. The difference is that the rvalue reference version of push_back
calls move
to pass its parameter to construct
. As we’ve seen, the construct
function uses the type of its second and subsequent arguments to determine which constructor to use. Because move
returns an rvalue reference, the type of the argument to construct
is string&&
. Therefore, the string
move constructor will be used to construct a new last element.
When we call push_back
the type of the argument determines whether the new element is copied or moved into the container:
StrVec vec; // empty StrVec
string s = "some string or another";
vec.push_back(s); // calls push_back(const string&)
vec.push_back("done"); // calls push_back(string&&)
These calls differ as to whether the argument is an lvalue (s
) or an rvalue (the temporary string
created from "done")
. The calls are resolved accordingly.
Rvalue and Lvalue Reference Member Functions
Ordinarily, we can call a member function on an object, regardless of whether that object is an lvalue or an rvalue. For example:
string s1 = "a value", s2 = "another";
auto n = (s1 + s2).find('a');
Here, we called the find
member (§ 9.5.3, p. 364) on the string
rvalue that results from adding two string
s. Sometimes such usage can be surprising:
s1 + s2 = "wow!";
Here we assign to the rvalue result of concatentating these string
s.
Prior to the new standard, there was no way to prevent such usage. In order to maintain backward compatability, the library classes continue to allow assignment to rvalues, However, we might want to prevent such usage in our own classes. In this case, we’d like to force the left-hand operand (i.e., the object to which this
points) to be an lvalue.
We indicate the lvalue/rvalue property of this
in the same way that we define const
member functions (§ 7.1.2, p. 258); we place a reference qualifier after the parameter list:
class Foo {
public:
Foo &operator=(const Foo&) &; // may assign only to modifiable lvalues
// other members of Foo
};
Foo &Foo::operator=(const Foo &rhs) &
{
// do whatever is needed to assign rhs to this object
return *this;
}
The reference qualifier can be either &
or &&
, indicating that this
may point to an rvalue or lvalue, respectively. Like the const
qualifier, a reference qualifier may appear only on a (nonstatic
) member function and must appear in both the declaration and definition of the function.
We may run a function qualified by &
only on an lvalue and may run a function qualified by &&
only on an rvalue:
Foo &retFoo(); // returns a reference; a call to retFoo is an lvalue
Foo retVal(); // returns by value; a call to retVal is an rvalue
Foo i, j; // i and j are lvalues
i = j; // ok: i is an lvalue
retFoo() = j; // ok: retFoo() returns an lvalue
retVal() = j; // error: retVal() returns an rvalue
i = retVal(); // ok: we can pass an rvalue as the right-hand operand to assignment
A function can be both const
and reference qualified. In such cases, the reference qualifier must follow the const
qualifier:
class Foo {
public:
Foo someMem() & const; // error: const qualifier must come first
Foo anotherMem() const &; // ok: const qualifier comes first
};
Overloading and Reference Functions
Just as we can overload a member function based on whether it is const
(§ 7.3.2, p. 276), we can also overload a function based on its reference qualifier. Moreover, we may overload a function by its reference qualifier and by whether it is a const
member. As an example, we’ll give Foo
a vector
member and a function named sorted
that returns a copy of the Foo
object in which the vector
is sorted:
class Foo {
public:
Foo sorted() &&; // may run on modifiable rvalues
Foo sorted() const &; // may run on any kind of Foo
// other members of Foo
private:
vector<int> data;
};
// this object is an rvalue, so we can sort in place
Foo Foo::sorted() &&
{
sort(data.begin(), data.end());
return *this;
}
// this object is either const or it is an lvalue; either way we can't sort in place
Foo Foo::sorted() const & {
Foo ret(*this); // make a copy
sort(ret.data.begin(), ret.data.end()); // sort the copy
return ret; // return the copy
}
When we run sorted
on an rvalue, it is safe to sort the data
member directly. The object is an rvalue, which means it has no other users, so we can change the object itself. When we run sorted
on a const
rvalue or on an lvalue, we can’t change this object, so we copy data
before sorting it.
Overload resolution uses the lvalue/rvalue property of the object that calls sorted
to determine which version is used:
retVal().sorted(); // retVal() is an rvalue, calls Foo::sorted() &&
retFoo().sorted(); // retFoo() is an lvalue, calls Foo::sorted() const &
When we define const
memeber functions, we can define two versions that differ only in that one is const
qualified and the other is not. There is no similar default for reference qualified functions. When we define two or more members that have the same name and the same parameter list, we must provide a reference qualifier on all or none of those functions:
class Foo {
public:
Foo sorted() &&;
Foo sorted() const; // error: must have reference qualifier
// Comp is type alias for the function type (see § 6.7 (p. 249))
// that can be used to compare int values
using Comp = bool(const int&, const int&);
Foo sorted(Comp*); // ok: different parameter list
Foo sorted(Comp*) const; // ok: neither version is reference qualified
};
Here the declaration of the const
version of sorted
that has no parameters is an error. There is a second version of sorted
that has no parameters and that function has a reference qualifier, so the const
version of that function must have a reference qualifier as well. On the other hand, the versions of sorted
that take a pointer to a comparison operation are fine, because neither function has a qualifier.
INFO
If a member function has a reference qualifier, all the versions of that member with the same parameter list must have reference qualifiers.
INFO
Exercises Section 13.6.3
Exercise 13.55: Add an rvalue reference version of push_back
to your StrBlob
.
Exercise 13.56: What would happen if we defined sorted
as:
Foo Foo::sorted() const & {
Foo ret(*this);
return ret.sorted();
}
Exercise 13.57: What if we defined sorted
as:
Foo Foo::sorted() const & { return Foo(*this).sorted(); }
Exercise 13.58: Write versions of class Foo
with print statements in their sorted
functions to test your answers to the previous two exercises.