GCO4020/CSC428 - Advanced Object Oriented Techniques In C++
Week 4

 

Topic 7: Reference Counting

Solutions to Exercises


Synopsis


Solution to Exercise 2

One of the most fundamental object-oriented design principles ("encapsulation¤") is that the implementation¤ details of a class should be private to that class. Generally speaking this means that all access to information stored in the class should be via public member functions (which then define the allowable behaviours of the class).

The reason for this separation of interface¤ (public functions) and implementation¤ (private data and functions) is simple: if at some future time the implementation¤ has to be changed (either due to a mistake or omission in the original design, or to improve efficiency, or to provide some new functionality), any code which uses the class will be isolated from the effects of the changes.

For example, if we suddenly realized that we could not store the guarded pointers directly in RefCount objects, but needed to store them in some form of database, to which each RefCount stored a pointer, then we could simply replace the RefCount::myPtr data member with a new member (perhaps RefCount::myDatabase) and rewrite the body of RefCount::Ptr() to do the appropriate search and retrieval through the database pointer. So long as the signature (that is, the name, argument list and return type) of RefCount::Ptr() was not altered, all the code using that member function would continue to work without alteration, regardless of the internal changes made to class RefCount.

On the other hand, if we had originally made RefCount::myPtr publicly accessible, then we would have to find every place in all our code where that data member is referred to, and replace each such reference with the complete code needed to do the database lookup.

(End of solution)  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


Solution to Exercise 3

  1. The specialization¤ of RefCount::Clone() is sufficient to handle the char* case because char*s are almost never used to point to dynamically allocated single characters. However, pointers to user-defined object types are generally almost equally likely to be used to point at single objects or at arrays of them, meaning that the user will sometimes have to choose whether (and how) to specialize RefCount::Clone() for their newly defined class type.  
     
    Worse still, in some programs the user may wish to use reference counted points to both single objects and arrays, making it impossible to choose between specializations¤ of RefCount::Clone(), since both are required.  
     

  2. The solution to this problem is to create two separate reference counting classes, for example: RefCountSingle and RefCountArray. The "Single" version would simply provide a standard Clone() member based on the corresponding copy constructor, whilst the Clone() member for the "Array" version would provide some form of iterated copy, which could be specialized as necessary.  
     
    If this solution were adopted, a single RefCountedPtrTo class could still be provided. The only difference would be that, instead of being templated on a single type (that is, the underlying pointer type), this version of RefCountedPtrTo would take two type parameters: the underlying pointer type and the type of reference counter to use. For example:

class MyType { // ETC. };

template <> RefCountArray<MyType>::Clone(void) { // IF NEEDED }

typedef RefCountedPtrTo<MyType, RefCountSingle<MyType> > MySinglePtr; typedef RefCountedPtrTo<MyType, RefCountArray<MyType> > MyArrayPtr;

MySinglePtr myInstance = new MyType; MyArrayPtr myInstances = new MyType[100];

(End of solution)  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


Solution to Exercise 4

  1. The RefCountedPtrTo dereferencing operators provide direct access to the underlying memory being guarded. For example it is possible to directly delete that memory (bypassing the reference counter) by "re-referencing" the dereferenced pointers using the "address-of" operator (&):  
     
            RefCountedPtrTo<MyType> ptr = new MyType;  
     
            delete &(*ptr);   // OR: delete &(ptr[0]);  
     
    Worse still, the reference counter would be unaware of this circumvention and would still attempt to reclaim the guarded memory itself when ptr went out of scope, resulting in the memory being "re-deleted" and the free store almost certainly corrupted.  
     

  2. One solution to this problem would be not offer direct access to the guarded pointer via the dereferencing operators, but rather to have them return proxies which could prevent the sneaky "re-referencing" shown above (see source code listing: RefCountProxy.C).

(End of solution)  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


Solution to Exercise 5

The entire purpose of a RefCount object is to track changes in the number of references to the dynamically-allocated memory it guards. Since it is impossible for a RefCount object to track these changes if it is itself unchangable, it follows that, to perform it's function, a RefCount object can never usefully be declared const.

(End of solution)  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


Solution to Exercise 6

If RefCountedPtrTo::operator= did not check for assignment-to-self, when a RefCountedPtrTo was assigned to itself, it would decrement its reference count:

        myRefCount->Decr();

and then immediately increment it again (through p):

        myRefCount = p.myRefCount->Incr();

In the (reasonably common) situation where the pointer's reference count was initially 1, this sequence would first decrement it to zero, causing the counter and it's guarded memory to be reclaimed. The subsequent increment (on a non-existent reference counter) would be undefined (possibly fatally so), as would all future operations through the counter.

 


This material is part of the GCO4020/CSC428 - Advanced Object Oriented Techniques In C++ course.
Copyright © Damian Conway, 1997. All rights reserved.

Last updated: Fri Feb 18 11:17:29 2000