Although copy control is most often needed for classes that allocate resources, resource management is not the only reason why a class might need to define these members. Some classes have bookkeeping or other actions that the copy-control members must perform.
As an example of a class that needs copy control in order to do some bookkeeping, we’ll sketch out two classes that might be used in a mail-handling application. These classes, Message
and Folder
, represent, respectively, email (or other kinds of) messages, and directories in which a message might appear. Each Message
can appear in multiple Folder
s. However, there will be only one copy of the contents of any given Message
. That way, if the contents of a Message
are changed, those changes will appear when we view that Message
from any of its Folder
s.
To keep track of which Message
s are in which Folder
s, each Message
will store a set
of pointers to the Folder
s in which it appears, and each Folder
will contain a set
of pointers to its Message
s. Figure 13.1 illustrates this design.
Figure 13.1. Message
and Folder
Class Design
Our Message
class will provide save
and remove
operations to add or remove a Message
from a specified Folder
. To create a new Message
, we will specify the contents of the message but no Folder
. To put a Message
in a particular Folder
, we must call save
.
When we copy a Message
, the copy and the original will be distinct Message
s, but both Message
s should appear in the same set
of Folder
s. Thus, copying a Message
will copy the contents and the set
of Folder
pointers. It must also add a pointer to the newly created Message
to each of those Folder
s.
When we destroy a Message
, that Message
no longer exists. Therefore, destroying a Message
must remove pointers to that Message
from the Folder
s that had contained that Message
.
When we assign one Message
to another, we’ll replace the contents
of the left-hand Message
with those in the right-hand side. We must also update the set
of Folder
s, removing the left-hand Message
from its previous Folder
s and adding that Message
to the Folder
s in which the right-hand Message
appears.
Looking at this list of operations, we can see that both the destructor and the copy-assignment operator have to remove this Message
from the Folder
s that point to it. Similarly, both the copy constructor and the copy-assignment operator add a Message
to a given list of Folder
s. We’ll define a pair of private
utility functions to do these tasks.
The copy-assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in
private
utility functions.
The Folder
class will need analogous copy control members to add or remove itself from the Message
s it stores.
We’ll leave the design and implementation of the Folder
class as an exercise. However, we’ll assume that the Folder
class has members named addMsg
and remMsg
that do whatever work is need to add or remove this Message
, respectively, from the set of messages in the given Folder
.
Message
ClassGiven this design, we can write our Message
class as follows:
class Message {
friend class Folder;
public:
// folders is implicitly initialized to the empty set
explicit Message(const std::string &str = ""):
contents(str) { }
// copy control to manage pointers to this Message
Message(const Message&); // copy constructor
Message& operator=(const Message&); // copy assignment
~Message(); // destructor
// add/remove this Message from the specified Folder's set of messages
void save(Folder&);
void remove(Folder&);
private:
std::string contents; // actual message text
std::set<Folder*> folders; // Folders that have this Message
// utility functions used by copy constructor, assignment, and destructor
// add this Message to the Folders that point to the parameter
void add_to_Folders(const Message&);
// remove this Message from every Folder in folders
void remove_from_Folders();
};
The class defines two data members: contents
, to store the message text, and folders
, to store pointers to the Folder
s in which this Message
appears. The constructor that takes a string
copies the given string
into contents
and (implicitly) initializes folders
to the empty set. Because this constructor has a default argument, it is also the Message
default constructor (§ 7.5.1, p. 290).
save
and remove
MembersAside from copy control, the Message
class has only two public
members: save
, which puts the Message
in the given Folder
, and remove
, which takes it out:
void Message::save(Folder &f)
{
folders.insert(&f); // add the given Folder to our list of Folders
f.addMsg(this); // add this Message to f's set of Messages
}
void Message::remove(Folder &f)
{
folders.erase(&f); // take the given Folder out of our list of Folders
f.remMsg(this); // remove this Message to f's set of Messages
}
To save (or remove) a Message
requires updating the folders
member of the Message
. When we save
a Message
, we store a pointer to the given Folder
; when we remove
a Message
, we remove that pointer.
These operations must also update the given Folder
. Updating a Folder
is a job that the Folder
class controls through its addMsg
and remMsg
members, which will add or remove a pointer to a given Message
, respectively.
Message
ClassWhen we copy a Message
, the copy should appear in the same Folder
s as the original Message
. As a result, we must traverse the set
of Folder
pointers adding a pointer to the new Message
to each Folder
that points to the original Message
. Both the copy constructor and the copy-assignment operator will need to do this work, so we’ll define a function to do this common processing:
// add this Message to Folders that point to m
void Message::add_to_Folders(const Message &m)
{
for (auto f : m.folders) // for each Folder that holds m
f->addMsg(this); // add a pointer to this Message to that Folder
}
Here we call addMsg
on each Folder
in m.folders
. The addMsg
function will add a pointer to this Message
to that Folder
.
The Message
copy constructor copies the data members of the given object:
Message::Message(const Message &m):
contents(m.contents), folders(m.folders)
{
add_to_Folders(m); // add this Message to the Folders that point to m
}
and calls add_to_Folders
to add a pointer to the newly created Message
to each Folder
that contains the original Message
.
Message
DestructorWhen a Message
is destroyed, we must remove this Message
from the Folder
s that point to it. This work is shared with the copy-assignment operator, so we’ll define a common function to do it:
// remove this Message from the corresponding Folders
void Message::remove_from_Folders()
{
for (auto f : folders) // for each pointer in folders
f->remMsg(this); // remove this Message from that Folder
}
The implementation of the remove_from_Folders
function is similar to that of add_to_Folders
, except that it uses remMsg
to remove the current Message
.
Given the remove_from_Folders
function, writing the destructor is trivial:
Message::~Message()
{
remove_from_Folders();
}
The call to remove_from_Folders
ensures that no Folder
has a pointer to the Message
we are destroying. The compiler automatically invokes the string
destructor to free contents
and the set
destructor to clean up the memory used by those members.
Message
Copy-Assignment OperatorIn common with most assignment operators, our Folder
copy-assignment operator must do the work of the copy constructor and the destructor. As usual, it is crucial that we structure our code to execute correctly even if the left- and right-hand operands happen to be the same object.
In this case, we protect against self-assignment by removing pointers to this Message
from the folders
of the left-hand operand before inserting pointers in the folders
in the right-hand operand:
Message& Message::operator=(const Message &rhs)
{
// handle self-assignment by removing pointers before inserting them
remove_from_Folders(); // update existing Folders
contents = rhs.contents; // copy message contents from rhs
folders = rhs.folders; // copy Folder pointers from rhs
add_to_Folders(rhs); // add this Message to those Folders
return *this;
}
If the left- and right-hand operands are the same object, then they have the same address. Had we called remove_from_folders
after calling add_to_folders
, we would have removed this Message
from all of its corresponding Folder
s.
swap
Function for Message
The library defines versions of swap
for both string
and set
(§ 9.2.5, p. 339). As a result, our Message
class will benefit from defining its own version of swap
. By defining a Message
-specific version of swap
, we can avoid extraneous copies of the contents
and folders
members.
However, our swap
function must also manage the Folder
pointers that point to the swapped Messages
. After a call such as swap(m1, m2)
, the Folder
s that had pointed to m1
must now point to m2
, and vice versa.
We’ll manage the Folder
pointers by making two passes through each of the folders
members. The first pass will remove the Message
s from their respective Folder
s. We’ll next call swap
to swap the data members. We’ll make the second pass through folder
s this time adding pointers to the swapped Message
s:
void swap(Message &lhs, Message &rhs)
{
using std::swap; // not strictly needed in this case, but good habit
// remove pointers to each Message from their (original) respective Folders
for (auto f: lhs.folders)
f->remMsg(&lhs);
for (auto f: rhs.folders)
f->remMsg(&rhs);
// swap the contents and Folder pointer sets
swap(lhs.folders, rhs.folders); // uses swap(set&, set&)
swap(lhs.contents, rhs.contents); // swap(string&, string&)
// add pointers to each Message to their (new) respective Folders
for (auto f: lhs.folders)
f->addMsg(&lhs);
for (auto f: rhs.folders)
f->addMsg(&rhs);
}
Exercises Section 13.4
Exercise 13.33: Why is the parameter to the
save
andremove
members ofMessage
aFolder&?
Why didn’t we define that parameter asFolder
? Orconst Folder&?
Exercise 13.34: Write the
Message
class as described in this section.Exercise 13.35: What would happen if
Message
used the synthesized versions of the copy-control members?Exercise 13.36: Design and implement the corresponding
Folder
class. That class should hold aset
that points to theMessage
s in thatFolder
.Exercise 13.37: Add members to the
Message
class to insert or remove a givenFolder*
intofolders
. These members are analogous toFolder
’saddMsg
andremMsg
operations.Exercise 13.38: We did not use copy and swap to define the
Message
assignment operator. Why do you suppose this is so?