Virtual Constructors and Prototype





Item 29. Virtual Constructors and Prototype

Suppose you find yourself in a Swedish restaurant, and you'd like to order a meal. Unfortunately, your knowledge of Swedish is limited to technical correspondence, cursing, or (typically) a combination of the two. The menu is in Swedish, and you can't read Swedish, but you do notice a gentleman on the other side of the room who is really enjoying his meal. Therefore, you call over the waiter and say

If that gentleman is having fish, I'd like fish. If he's having spaghetti, I'd like spaghetti too. Otherwise, if he's having eel, then eel it is. However, if he has decided on the preserved kumquats, then I'll have those.

Does this sound reasonable? Of course not. (For one thing, you probably don't want to order spaghetti in a Swedish restaurant.) This procedure has two basic problems. First, it's tedious and inefficient. Second, it can fail. What would happen if you come to the end of your sequence of questions and you still haven't been able to guess what the other diner is eating? The waiter will walk off, leaving you stranded and hungry. Even if you happen to know the entire content of the menu and are therefore guaranteed of (eventual) success, your list of questions may become invalid or incomplete if the menu is modified between your visits to the restaurant.

The proper approach, of course, is simply to call the waiter over and say, "I'd like what that gentleman is having."

If the waiter is a literalist, he'll snatch up the other diner's half-finished meal and bring it to your table. However, that sort of behavior can lead to hurt feelings and even a food fight. This is the sort of unpleasantness that can occur when two diners try to consume the same meal at the same time. If he knows his business, the waiter will deliver an exact copy of the other diner's meal to you, without affecting the state of the meal that is copied.

These are the two major reasons for cloning: You must (or you prefer to) remain in ignorance about the precise type of object you're dealing with, and you don't want to effect change or be affected by changes to the original object.

A member function that provides the ability to clone an object is traditionally called a "virtual constructor" in C++. Of course, there are no virtual constructors, but producing a copy of an object generally involves indirect invocation of its class's copy constructor through a virtual function, giving the effectif not the realityof virtual construction. More recently, this technique has been called an instance of the Prototype pattern.

Of course, we have to know something about the object to which we refer. In our case, we know that what we want is-a meal.

class Meal {
   public:
     virtual ~Meal();
     virtual void eat() = 0;
     virtual Meal *clone() const = 0;
     //...
};

The Meal type provides the ability to clone with the clone member function. The clone function is actually a specialized kind of Factory Method (see Factory Method [30, 103]) that manufactures an appropriate product while allowing the invoking code to remain in ignorance of the exact type of context and product class. Concrete classes derived from Meal (that is, those meals that actually exist and are listed on the menu) must provide an implementation of the pure virtual clone operation.

class Spaghetti : public Meal {
   public:
     Spaghetti( const Spaghetti & ); // copy ctor
     void eat();
     Spaghetti *clone() const
         { return new Spaghetti( *this ); } // call copy ctor
     //...
};

(For an explanation as to why the return type of the overriding derived class clone function differs from that of the base class function, see Covariant Return Types [31, 107].)

With this simple framework in place, we have the ability to produce a copy of any type of Meal without precise knowledge about the actual type of the Meal we're copying. Note that the following code has no mention of concrete derived classes and therefore no coupling of the code to any current or future types derived from Meal.

const Meal *m = thatGuysMeal(); // whatever he's having...
Meal *myMeal = m->clone(); // ...I want one too!

In fact, we could end up ordering something we've never even heard of. In effect, with Prototype, ignorance of the existence of a type is no barrier to creating an object of that type. The polymorphic code above can be compiled and distributed, and later augmented with new types of Meal without the need for recompilation.

This example illustrates some of the advantages of ignorance in software design, particularly in the design of software structured as a framework that is designed for customization and extension: The less you know, the better.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows