Zend certified PHP/Magento developer

C++ Primer 5th Edition, Part 4: What Makes a Good Example?

Last week, I said that one of Barbara’s challenges in rewriting the C++ Primer was to find useful programming examples to illustrate language features that C++11 had made easier to use. I think copy constructors are a particularly interesting case.

A useful example of a language feature should give the reader an idea of how mdas; and why — one might use that feature in practice. It doesn’t have to be complete, but it does have to be detailed enough to let readers understand how they might complete it. In the case of a copy constructor, there are some additional requirements:

  • The example must do what a copy constructor does, namely create a copy of an object.
  • It must do something other than just copy the object’s data members, because that’s what happens by default.
  • It must do something other than copying a pointer and incrementing a reference count, because that’s what shared_ptr does.

The first of these requirements tends to conflict with the others. The reason is that the whole notion of a copy is that the copy can be used interchangeably with the original. How can one object be equivalent to another and yet not have their data members be copies of each other?

One plausible answer to this question is to define a data structure that keeps track of all objects of a given type. For example, suppose our class is called Thing. We might use a set of Thing pointers for the purpose:

 
     class Thing {
     private:
           static setThing* allThings;
           // …
     };

Every Thing constructor would add this to the set; the destructor would remove it. This strategy would maintain the invariant that allThings contains the address of every Thing in existence.

This example is useful as far as it goes. However, it has a drawback: Every constructor manipulates the set in the same way, and if we take that manipulation out of the picture, we’re left with the default copy constructor. So this example doesn’t actually do anything particularly interesting in the copy constructor. However, we can make this example more interesting by generalizing it. This particular generalization actually appeared in the fourth edition of the primer, but it takes on new importance in the fifth edition.

The idea is to imagine a program that deals with email messages. Messages can be stored in mailboxes, which indicate how the user has characterized those messages. A single message can be stored in more than one mailbox, and of course a mailbox can store any number of messages. In such a system, one might copy a message because one intends to edit it. Similarly, one might copy a mailbox because one intends to add messages to or remove them from the mailbox.

This message/mailbox example offers some genuinely interesting design questions to explore. Presumably copying a message should leave the copy in the same mailbox(es) as the original. Similarly, copying a mailbox should probably leave the new mailbox holding the same messages as the original, rather than copying all of the messages in the mailbox. Moreover, the example suggests interesting questions to ask about implementation. For example, if deleting a message removes it from all mailboxes that contain it, how does the destructor go about finding the locations of those mailboxes? Does assigning one message to another change the mailboxes to which the left-hand side belongs? Why or why not? What about swapping two messages?

It may seem silly to devote this much effort to finding the right examples. Why not just explain what a copy constructor does and be done with it? The answer, I think, is that the right examples are crucially important to many people’s learning. If the examples are too simple, readers don’t learn much from them. If they are too complicated, readers don’t understand them, so the readers don’t learn anything at all. The trick is to find examples that are rich enough to avoid triviality, but not so complicated as to preclude understanding.