Types of safe pointers

The CommonPoint system provides five concrete classes which may be used to declare safe pointers. Figure 1 shows the inheritance hierarchy for safe pointer class templates.


Overview of safe pointers

Each of these template classes is a wrapper for a pointer. Instances of safe pointers may be used wherever you might use a pointer (for example, instead of AType*, you write TInstanceOf<AType>). Like a raw pointer, a safe pointer can be dereferenced using operator * or operator ->. A safe pointer can have the value NIL (0), however, dereferencing a NIL safe pointer throws an exception. Dereferencing a NIL raw pointer, on the other hand, usually results in an attempt to interpret the memory at address 0 as an object, with unpredictable results

Each concrete safe pointer class implements an ownership policy--a convention for what code is responsible for destroying the object to which it points (and returning the object's storage to the heap).

TAliasTo<> -- The pointer does not own the object.

TInstanceOf<> -- The pointer owns the object, permanently.

TOnlyPointerTo<> -- The pointer owns the object, but may transfer ownership to another pointer.

TDeleterFor<> -- The pointer owns the object, but may transfer ownership to another pointer. TDeleterFor is very similar to TOnlyPointerTo in both syntax and semantics. TDeleterFor is actually an older implementation of a simple safe pointer. You will find it used more frequently in CommonPoint applications. However eventually use of TDeleterFor is expected to give way to TOnlyPointerTo

TDeleterForArrayOf<> -- The pointer owns the object, but may transfer ownership to another pointer.

By using safe pointers, you can clearly declare the storage management paradigm for a particular pointer, and exploit automatic object destruction (when exiting a scope, or destroying a containing object) to handle storage management or reference counting.

Safe pointers are readily convertible to and from raw pointers, although the conversion to raw pointers must be explicit. Safe pointers are implemented using non-virtual functions, and all code is in-line. In-line function definitions make safe pointers as efficient as raw pointers (at run time), except the dereferencing operators (* and ->) take more code and time (to check for NIL) than dereferencing a raw pointer.

Safe pointers are not thread-safe. Concurrent use of a wrapper object by several threads is an error; the results are not defined.

Using safe pointers

Before going into the details of interface protocols for safe pointer classes, you may find it helpful to study examples using safe pointers in this section.

It's helpful to see how TDeleterFor is used in context. One of the benefits of using a TDeleterFor pointer is that it automates cleanup of referenced objects if an exception occurs before the pointer goes out of scope. In the following example from the StockBrowser example program a TDeleterFor is used to reference a subset of stock entries copied from the persistent DiskDictionary which holds all stock trading records.

          TDeleterFor<TCollectionOf<TStockDay> > diskCollection =
              fDiskDictionary->Copy(stockName);
Now diskCollection is a smart pointer which can use the same syntax as if it had been declared as a normal C++ pointer to a TCollectionOf<TStockDay> object. A TStockDay is a data entry for a single day's trading information for a given company. The diskCollection smart pointer currently refers to a collection of such entries for a single company. Semantically, smart pointers are essentially the same as normal pointers, but smart pointers do extra house keeping and memory management. The intent of the function from which this code is extracted is to iterate over the collection of stock day entries for a company and select entries that fall within a specified range of dates. If an exception occurs, or program control reaches the end of the block in which diskCollection is declared, code in TDeleterFor is invoked automatically to destroy the collection referred to by diskCollection.

Another common use of TDeleterFor smart pointers is with iterators. In the same member function declaring diskCollection, an iterator is used to walk through each stock day entry and select it into a target collection if the entry falls within the desired range of dates. The iterator is created as follows:

              TDeleterFor<TIteratorOver<TStockDay> > iterator =
                  diskCollection->CreateIterator();
Again, if an exception occurs before iteration is complete, automatic cleanup of heap objects owned by the iterator is guaranteed. Otherwise cleanup occurs when the iterator goes out of scope. Here's the entire function from the StockBrowser application so you can see a typical context for using safe pointers.

      bool
      TStockServer::CopyStockData(
              const TStandardText& stockName,
              TCollectionOf<TStockDay>& collectionToFill,
              const TRangeOfDays& rangeOfDays)
      {
          bool success = false;
          TDeleterFor<TCollectionOf<TStockDay> > diskCollection =
              fDiskDictionary->Copy(stockName);
          if (diskCollection != NIL) {
              TDeleterFor<TIteratorOver<TStockDay> > iterator =
                  diskCollection->CreateIterator();
              for (TStockDay* item = iterator->First();
                  item != NIL;
                  item = iterator->Next()) {
      
                  if (rangeOfDays.Contains(item->GetDate()))
                      collectionToFill.Add(item);
              }
              success = collectionToFill.Count() > 0;
          }
          return success;
      }
Arguments passed to CopyStockData include the name of the company (stockName), the target collection into which the selected stock day entries are to be placed (collectionToFill), and the range of days used to select stock day entries (rangeOfDays).

The previous example is a case in which ownership of the referenced object is retained throughout the life of the smart pointer. Sometimes, however, smart pointers adopt or orphan referenced objects. The main benefit of using a smart pointer to create an object and then orphan it (give up the referenced object to another pointer) is to assure cleanup in the event of an exception.

The following example shows how a graphic object used in the display of the StockBrowser is created using a TGraphicGroup. A smart pointer is declared to point to a newly created group.

          TDeleterFor<TGraphicGroup> group = new TGraphicGroup;
Several bundles of graphics objects are then added to this group. The bundles, which contain colors and pens used to render the graphic, are referenced by a second smart pointer.

          TDeleterFor<TGrafBundle> bundle = new
              TGrafBundle(*TColorPaint::GetWhite().GetColor());
The bundles both adopt and orphan objects. For example, bundle adopts a pen for rendering the frame of the graphic, then the entire bundle is orphaned by the bundle smart pointer and adopted by a pointer to an MGraphic object. This Mgraphic pointer is a regular C++ pointer called graphic1. The object referenced by graphic1 is ultimately adopted by the TGraphicGroup object referenced by the group smart pointer.

              bundle->AdoptFramePen(new TSolidPen(kWidth, TPen::kInsetFrame));
              MGraphic* graphic1 = new TArea(bounds, bundle.OrphanObject());
              group->AdoptLast(graphic1);
The group actually winds up adopting two MGraphic objects. Before this occurs, the MGraphic objects themselves adopt TGrafBundles. Tracking ownership and adoption of objects is critical because the pointer which winds up owning an object is the one and only entity ultimately responsible for deleting it. The entire function is listed here for reference.

      MGraphic*
      TViewerContentView::CreateDropFeedback(const TModelSelection& selection) const
      {
          static const GCoordinate kWidth = 1;
      
          TDeleterFor<TGraphicGroup> group = new TGraphicGroup;
          if (selection.IsDefined()) {
              TGRect nameBounds(fNameDisplay.GetTextBounds());
              nameBounds.Inset(TGPoint(1.0, 1.0));
              TGArea bounds(nameBounds);
              TDeleterFor<TGrafBundle> bundle = new
                  TGrafBundle(*TColorPaint::GetWhite().GetColor());
              bundle->AdoptFramePen(new TSolidPen(kWidth, TPen::kInsetFrame));
              MGraphic* graphic1 = new TArea(bounds, bundle.OrphanObject());
              group->AdoptLast(graphic1);
              bundle = new TGrafBundle(*TColorPaint::GetBlack().GetColor());
              bundle->AdoptFramePen(new TSolidPen(kWidth, TPen::kOutsetFrame));
              MGraphic* graphic2 = new TArea(bounds, bundle.OrphanObject());
              group->AdoptLast(graphic2);
          }
          return group.OrphanObject();
      }
The main advantage here to passing references back and forth through smart pointers when orphaning and adopting objects is to guard against memory leaks in the event of an exception. This is different than the first example where automatic deletion of referenced objects played the main role in deciding to use smart pointers. Once an object is orphaned by a smart pointer, you need not worry that delete will be invoked for a dangling pointer referring to a non existent object or to undefined memory.

In some cases, you don't need smart pointers. Notice, for example, that both graphic1 and graphc2 are declared as normal C++ pointers to TArea objects.

              MGraphic* graphic1 = new TArea(bounds, bundle.OrphanObject());
              ...
              MGraphic* graphic2 = new TArea(bounds, bundle.OrphanObject());
The reason normal pointers are used instead of safe pointers is because the objects created and referenced by both graphic1 and graphic2 are immediately adopted by the TGraphicGroup referenced by group. This adoption occurs in a statement immediately following assignment to graphic1 or graphic2. Since no exception is likely to occur between the statement initializing the pointer and the statement transferring the referenced object to group, a smart pointer is not required.


[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker