Chapter 14 of Accelerated C++ continues the discussion of issues
related to memory management of objects of classes involved in subtype
polymorphism. First, a Handle class is introduced that has
essentially the same interface as built-in pointers (e.g.,
dereferencing with * and ->, implicit conversion to
bool when used as a condition, etc.), but which takes care of
memory management of the objects pointed to. A key issue with such pseudo-pointer class objects is whether copying or assigning one to another creates a new copy of the object pointed to or simply shares it. This Handle class always creates a new copy. The next version, called Ref_handle, embodies the opposite design decision, to always share the objects pointed to. This requires maintaining "reference counts," which are integer objects in which we keep track of how many Ref_handle objects are pointing to the same data object. A key design decision here is where to keep the reference counts: if we assumed the data objects to which Ref_handles point must have a data member to hold the reference count, that would limit the usefulness of Ref_handles to cases in which we have control over the source code for data object classes. Instead, it's better to allocate the reference counts as separate objects on the heap, and manage these objects alongside the data objects. The resulting Ref_handle class implements a policy of always sharing the objects pointed to, which saves memory and copying expense but can also lead to undesired consequences when a program makes a change to data through a Ref_handle and then "sees" the change appearing also when accessing through another Ref_handle that shared the data. If the programmer was thinking that copying a Ref_handle would create a separate copy of the data, such a result would be unexpected and could easily lead to run-time errors in later computations. Thus, the last version of a pseudo-pointer class developed, the Ptr class, provides for both sharing with reference counting and copying, with the decision about which to do being made automatically according to whether the data object is changed by an operation or is returned in a way that it could be subject to later change. E.g., in a re-implementation of the Str class using Ptr<Vec<char> >, the operator+= implementation changes the underlying Vec<char> data, and hence it first creates a new copy, separating it from that pointed to by other Ptr objects, and makes the change only to the new copy. An interesting issue that comes up here is how to do the copying polymorphically. Since copy constructors cannot themselves be made virtual members of a class, the authors had previously introduced a clone virtual member in the Core class that could be overridden in the derived Grad class; each of these functions was defined to use the corresponding class's copy constructor. But if Ptr had to call clone as a member function, this solution would seem to limit the use of Ptr to point to objects of classes for which we have control of the source code and can insert clone members. The authors then present a neat solution that avoids the limitation, involving (full) specialization of a template member function. Most of the code discussed is available in the Accelerated C++ source code files. It deserves close study! |