Rogue Wave banner
Previous fileTop of DocumentContentsIndexNext file

7.3 Using the Handle-Body Classes

The handle-body idiom is a foundation-level pattern with many uses across a wide variety of domains. Rogue Wave designed the handle-body implementation in the Smart Pointer package especially for multithreaded programming, making it a useful tool with the other packages in Threads.h++ for building high-performance applications.

7.3.1 Understanding the Handle-Body Idiom

Among other benefits, the handle-body idiom allows you to simplify memory management without giving up efficiency, lets you decompose complex abstractions into simpler ones, and enables the separation of interfaces from implementations so they can vary independently.

7.3.1.1 Simplifying Memory Management

Often programmers face a quandary. They would like to pass objects by value, because it's simpler than passing pointers or references. When passing pointers or references, they have to worry about how the object is being shared throughout the application and where and when the object should be deleted. Worse, when passing a pointer to a graph of objects, the party responsible for deleting the objects may not have access to all the pointers necessary to do so, and even if it did, object graphs with arbitrary pointer relationships require great care not to delete the same pointer twice. None of this is a concern when passing objects by value.

Passing by value presents its own problems, however. It can be expensive. A heavyweight object can be orders of magnitude more expensive to copy than a simple pointer, which can usually be passed in a register. It can also be just plain wrong to pass by value. Sometimes it's important for the receiver of an object to be using the same instance as the sender, not a copy (when passing a stream, for example).

The handle-body idiom is an elegant solution for side-stepping this trade-off. With handle-body, you get the simplicity of value semantics with the efficiency and sharing of pointer semantics. This is achieved by putting lightweight handles in the public interface, which the user can pass by value. Each handle points to a body that holds the state and does the real work of the object. The methods on the handle are typically inline calls that simply forward the request to the body and return the result. The body also maintains a reference count to keep track of the number of handles pointing to it. As a handle is passed around an application, all copies of the handle point to the same body, with each copy increasing the reference count. When a handle goes out of scope, it decrements the body's reference count. If the count goes to zero, the body is deleted. Thus, the body is guaranteed to live as long as necessary, but no longer. This memory management is completely transparent to the user of the handle.

Be warned that the internal reference count, which you can only see by peering into a body object with a debugger, is one less than the number of handles pointing to the body. Thus, a body referenced by a single handle has an internal count of 0, a body shared by two handles has an internal count of 1, and so on.


NOTE: Member functions returning a body's reference count return the actual number of handles referencing the body, not the internal count shown in the debugger.

The diagrams in this chapter show the logical reference count - the number of handles referencing the body - not the internal private state you see in a debugger.

7.3.1.2 Decomposing Complex Abstractions

Occasionally a single object has different roles to play in different parts of an application. Threads.h++ is designed to give users of its objects the simplest interface possible that is suitable to their needs. A "fat" interface can be confusing, and it might include methods that are dangerous or inappropriate in a given context.

The handle-body idiom allows you to offer different interfaces, using separate handle classes, to the same body. Examples in Threads.h++ include escrows in the Interthread Communication package and threads themselves in the Threading package. Escrows are a thread-communication device that provides an area where one thread can write a result, which other threads can retrieve when ready. The Interthread Communication package provides two separate handle classes with minimal interfaces for the escrow implementation, one for reading and one for writing. Thread object implementations, too, have different handle classes for different contexts. When a thread has a handle to itself, it is of a different handle class than the class that would implement a handle to some other thread. In this way, the interface is tailored to prevent a thread from making a request on itself that requires it to take action before returning - a classic deadlock situation. The request won't return until the thread takes action, and the thread can't do anything until the request returns. Decomposing abstractions with handle-body keeps the programmer from accidentally falling into this trap.

7.3.1.3 Decoupling Interfaces from Implementations

Readers of Design Patterns (see the Bibliography) may recognize handle-body as an alias for the Bridge Pattern. The intent of Bridge is to "decouple an abstraction from its implementation so that the two can vary independently." While the full implications of the Bridge pattern are beyond the scope of this guide, the primary benefit, again, is simplicity. Handle-body allows for simplicity without giving up flexibility. Often the complexity of a class hierarchy stems from implementation considerations, like the desire to reuse certain code or variations in how an object must accomplish its task. In many cases, these variations can and should be hidden from the user. For example, a printer object should have the same simple interface regardless of the brand of printer that is currently selected. Behind the scenes, however, each printer probably requires its own class to issue commands peculiar to its brand. A single printer handle can point to any of many printer implementation bodies. Not only does the user deal with one simple interface, but the handle also takes care of which concrete body to instantiate. With a single hierarchy, on the other hand, the user would have to figure out which printer is currently selected in the application and instantiate the appropriate one.

Now imagine you want to extend your printer hierarchy by adding a two-sided printer class. Here's where the separation of interface from implementation really pays off. You can simply derive, say, handle class TwoSidedPrinter from handle class Printer. This is all the user needs to see. On the body side, you now have different brands, as well as one- and two-sided printers within those brands. Trying to do this all within one class hierarchy is difficult. Handle-body makes it clean. In Threads.h++, the use of handle-body in the Functor package is a prime example.

7.3.1.4 Handle-body Mechanics

The basic mechanics of handle-body can be visualized with the diagrams that follow. Figure 34 shows how the handle points and forwards calls to the body.

Figure 34 -- Model of interaction between handle and body classes

When you use a copy constructor to create one handle from another, for example when passing a handle as a parameter, the new handle is bound to the same body instance, if any, pointed-to by the other handle. Figure 35 shows the result.

Figure 35 -- Copy construction of handle-body classes

Similarly, assigning one handle to another causes the left-hand instance to detach from its current representation, if any, and then binds it to the same body instance pointed to by the right-hand instance. Figure 36 shows how assignment works in the same bicycle example.

Figure 36 -- Assignment of handle-body classes

7.3.2 Implementing Your Own Handles and Bodies

Handles and bodies can be implemented in various ways. This section provides two scenarios, followed by an example.

7.3.2.1 Defining Handle Classes that Automatically Create Bodies

Whenever possible, implement your handles to automatically create the appropriate body instance. This is easiest for users of your handle class. Sometimes there is a one-to-one correspondence between your handle and body classes. In those cases, it is trivial to create the right body to bind to the handle. Other times the choice of body depends on some context or the current environment. If so, you'll have to add some intelligence to your handle or use an external factory object.

For the body class:

  1. Derive your own body class from RWBodyBase. Add a suffix to it, to differentiate its name from the handle class name. (The convention for the Threads.h++ classes is that all body classes end in Imp.)

  2. To enforce that the bodies are created only by handles, make the body class's constructors protected and grant friendship to the handle class.

For the handle class:

  1. Derive your own handle class from RWHandleBase. In your class, replicate the interface of the body, taking into consideration the way handles and bodies work together:

  2. Override the body() method to return a reference to the most derived body.

7.3.2.2 Defining Handle Classes that Don't Create Bodies

Sometimes you have to leave it up to the user of your handle-body classes to choose a particular body for a given handle. For example, a Shape handle might be able to point to any of the bodies in a shape implementation hierarchy containing triangles, circles, rectangles, and so on. If your application is such that the Shape handle can't know which concrete shape to use for the body, the user has to decide. One approach is to give the body classes responsibility for creating handles, instead of the other way around as in the first scenario above. The bodies provide a public static make() member function that returns a handle bound to the body containing the make() function.

For the body class:

  1. Derive your own class hierarchy from RWBodyBase. Add suffixes, to mark the classes as bodies for use with a handle.

  2. Make the body constructors protected.

  3. For each constructor, implement an equivalent static make() function that creates an instance but returns a handle to that instance, instead of the instance itself. This ensures that bodies cannot be used independently, but only through the appropriate handle.

For the handle class:

  1. Create the handle class by deriving from RWHandleBase.

  2. Protect its constructor, so the only way to create a handle is through the body's static make() functions.

  3. Create a conversion constructor that converts a body pointer to a handle instance.

  4. Create member functions that forward their calls to the corresponding body implementations.

7.3.2.3 Handle-Body Example

Example 62 demonstrates the scenario given in Section 7.3.2.1, "Defining Handle Classes that Automatically Create Bodies." For the complete source code, see examples\thr0200osfam\pointer\HandleBodyEx1.cpp. (For comparison, the same example is also implemented according to the second guideline in HandleBodyEx2.cpp.)

The UML diagram in Figure 37 illustrates the model.

Figure 37 -- Bicycle example implementation

Example 62 -- Implementing a derived handle class that automatically creates a body

First implement BicycleImp, the bicycle body class.

//1The handle class must be a friend, because the body constructors are private.
//2The constructors are protected, so the only way to build bodies is through the handle.

Now implement the matching handle class, Bicycle, which duplicates the body's interface.

//1This default constructor does not instantiate a body on the heap. Any function applied to an instance created by this constructor throws an RWTHRInvalidPointer exception.
//2This constructor initializes a body on the heap, which is the correct way to build bodies.
//3The body() function is overridden to return a BicycleImp reference, using a type cast.
//4The handle implementation of showInformation() forwards the call to the body.

Previous fileTop of DocumentContentsIndexNext file

©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.