In addition to defining the copy-control members, classes that manage resources often also define a function named swap
(§ 9.2.5, p. 339). Defining swap
is particularly important for classes that we plan to use with algorithms that reorder elements (§ 10.2.3, p. 383). Such algorithms call swap
whenever they need to exchange two elements.
If a class defines its own swap
, then the algorithm uses that class-specific version. Otherwise, it uses the swap
function defined by the library. Although, as usual, we don’t know how swap
is implemented, conceptually it’s easy to see that swapping two objects involves a copy and two assignments. For example, code to swap two objects of our valuelike HasPtr
class (§ 13.2.1, p. 511) might look something like:
HasPtr temp = v1; // make a temporary copy of the value of v1
v1 = v2; // assign the value of v2 to v1
v2 = temp; // assign the saved value of v1 to v2
This code copies the string
that was originally in v1
twice—once when the HasPtr
copy constructor copies v1
into temp
and again when the assignment operator assigns temp
to v2
. It also copies the string
that was originally in v2
when it assigns v2
to v1
. As we’ve seen, copying a valuelike HasPtr
allocates a new string
and copies the string
to which the HasPtr
points.
In principle, none of this memory allocation is necessary. Rather than allocating new copies of the string
, we’d like swap
to swap the pointers. That is, we’d like swapping two HasPtr
s to execute as:
string *temp = v1.ps; // make a temporary copy of the pointer in v1.ps
v1.ps = v2.ps; // assign the pointer in v2.ps to v1.ps
v2.ps = temp; // assign the saved pointer in v1.ps to v2.ps
swap
FunctionWe can override the default behavior of swap
by defining a version of swap
that operates on our class. The typical implementation of swap
is:
class HasPtr {
friend void swap(HasPtr&, HasPtr&);
// other members as in § 13.2.1 (p. 511)
};
inline
void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i); // swap the int members
}
We start by declaring swap
as a friend
to give it access to HasPtr
’s (private
) data members. Because swap
exists to optimize our code, we’ve defined swap
as an inline
function (§ 6.5.2, p. 238). The body of swap
calls swap
on each of the data members of the given object. In this case, we first swap
the pointers and then the int
members of the objects bound to rhs
and lhs
.
Unlike the copy-control members,
swap
is never necessary. However, definingswap
can be an important optimization for classes that allocate resources.
swap
Functions Should Call swap
, Not std::swap
There is one important subtlety in this code: Although it doesn’t matter in this particular case, it is essential that swap
functions call swap
and not std::swap
. In the HasPtr
function, the data members have built-in types. There is no type-specific version of swap
for the built-in types. In this case, these calls will invoke the library std::swap
.
However, if a class has a member that has its own type-specific swap
function, calling std::swap
would be a mistake. For example, assume we had another class named Foo
that has a member named h
, which has type HasPtr
. If we did not write a Foo
version of swap
, then the library version of swap
would be used. As we’ve already seen, the library swap
makes unnecessary copies of the string
s managed by HasPtr
.
We can avoid these copies by writing a swap
function for Foo
. However, if we wrote the Foo
version of swap
as:
void swap(Foo &lhs, Foo &rhs)
{
// WRONG: this function uses the library version of swap, not the HasPtr version
std::swap(lhs.h, rhs.h);
// swap other members of type Foo
}
this code would compile and execute. However, there would be no performance difference between this code and simply using the default version of swap
. The problem is that we’ve explicitly requested the library version of swap
. However, we don’t want the version in std
; we want the one defined for HasPtr
objects.
The right way to write this swap
function is:
void swap(Foo &lhs, Foo &rhs)
{
using std::swap;
swap(lhs.h, rhs.h); // uses the HasPtr version of swap
// swap other members of type Foo
}
Each call to swap
must be unqualified. That is, each call should be to swap
, not std::swap
. For reasons we’ll explain in § 16.3 (p. 697), if there is a type-specific version of swap
, that version will be a better match than the one defined in std
. As a result, if there is a type-specific version of swap
, calls to swap
will match that type-specific version. If there is no type-specific version, then—assuming there is a using
declaration for swap
in scope—calls to swap
will use the version in std
.
Very careful readers may wonder why the using
declaration inside swap
does not hide the declarations for the HasPtr
version of swap
(§ 6.4.1, p. 234). We’ll explain the reasons for why this code works in § 18.2.3 (p. 798).
swap
in Assignment OperatorsClasses that define swap
often use swap
to define their assignment operator. These operators use a technique known as copy and swap. This technique swaps the left-hand operand with a copy of the right-hand operand:
// note rhs is passed by value, which means the HasPtr copy constructor
// copies the string in the right-hand operand into rhs
HasPtr& HasPtr::operator=(HasPtr rhs)
{
// swap the contents of the left-hand operand with the local variable rhs
swap(*this, rhs); // rhs now points to the memory this object had used
return *this; // rhs is destroyed, which deletes the pointer in rhs
}
In this version of the assignment operator, the parameter is not a reference. Instead, we pass the right-hand operand by value. Thus, rhs
is a copy of the right-hand operand. Copying a HasPtr
allocates a new copy of that object’s string
.
In the body of the assignment operator, we call swap
, which swaps the data members of rhs
with those in *this
. This call puts the pointer that had been in the left-hand operand into rhs
, and puts the pointer that was in rhs
into *this
. Thus, after the swap
, the pointer member in *this
points to the newly allocated string
that is a copy of the right-hand operand.
When the assignment operator finishes, rhs
is destroyed and the HasPtr
destructor is run. That destructor delete
s the memory to which rhs
now points, thus freeing the memory to which the left-hand operand had pointed.
The interesting thing about this technique is that it automatically handles self assignment and is automatically exception safe. By copying the right-hand operand before changing the left-hand operand, it handles self assignment in the same was as we did in our original assignment operator (§ 13.2.1, p. 512). It manages exception safety in the same way as the original definition as well. The only code that might throw is the new
expression inside the copy constructor. If an exception occurs, it will happen before we have changed the left-hand operand.
Assignment operators that use copy and swap are automatically exception safe and correctly handle self-assignment.
Exercises Section 13.3
Exercise 13.29: Explain why the calls to
swap
insideswap(HasPtr&, HasPtr&)
do not cause a recursion loop.Exercise 13.30: Write and test a
swap
function for your valuelike version ofHasPtr
. Give yourswap
a print statement that notes when it is executed.Exercise 13.31: Give your class a
<
operator and define avector
ofHasPtr
s. Give thatvector
some elements and thensort
thevector
. Note whenswap
is called.Exercise 13.32: Would the pointerlike version of
HasPtr
benefit from defining aswap
function? If so, what is the benefit? If not, why not?