Team LiB
Previous Section Next Section

13.3. Swap

 

In addition to defining the copy-control members, classes that manage resources often also define a function named swap9.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 HasPtrs 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

 

Writing Our Own swap Function

 

We 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.

 

Image Note

Unlike the copy-control members, swap is never necessary. However, defining swap can be an important optimization for classes that allocate resources.

 

 

swap Functions Should Call swap, Not std::swap

 
Image

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 strings 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 swap6.4.1, p. 234). We’ll explain the reasons for why this code works in § 18.2.3 (p. 798).

 

Using swap in Assignment Operators

 

Classes 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 deletes 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.

 

Image Tip

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 inside swap(HasPtr&, HasPtr&) do not cause a recursion loop.

Exercise 13.30: Write and test a swap function for your valuelike version of HasPtr. Give your swap a print statement that notes when it is executed.

Exercise 13.31: Give your class a < operator and define a vector of HasPtrs. Give that vector some elements and then sort the vector. Note when swap is called.

Exercise 13.32: Would the pointerlike version of HasPtr benefit from defining a swap function? If so, what is the benefit? If not, why not?


 
Team LiB
Previous Section Next Section