OSE - C++ Library User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Resource Management
OSE - C++ Library User GuideResource Management

Resource Management

1 Object Deletion

C++ does not provide garbage collection facilities. This forces you to deal with the problem of resource management. In particular, you must keep track of all objects allocated on the heap and know when they are no longer required. If object use is localised, this is not generally a problem. However, in a large system it is possible for objects to be referenced from quite distinct parts of an application. To determine if it is safe to delete an object, is not always a trivial task. A range of classes to assist in this task are provided.

2 Reference Counting

In reference counting, a count is maintained for each object indicating the number of parties interested in the object. When a new party becomes interested in an object, the count is incremented by one. When a party is no longer interested in an object the count is decremented by one. When the count is decremented to zero, the object will either be deleted immediately, or marked as dead and deleted at some later time, possibly as part of a garbage collection phase.

Incrementing and decrementing of the count may be either manual or automatic, depending on the implementation. Whether the reference count is manipulated manually or automatically, reference counting can only be performed for objects which have been allocated explicitly using `operator new()', and which can subsequently be destroyed using `operator delete()'. If reference counting is performed on objects not allocated using `operator new()', a number of problems can arise.

The first problem which can arise is that an object not allocated using `operator new()' is destroyed while the reference count is non zero. A non zero reference count indicates that a party was still interested in the object and may still try to access the object. Accessing an object which does not exist will result in undefined behaviour. The second problem which can arise is when the reference count is decremented to zero and an attempt is made to delete the object. If the object was not created using `operator new()' to begin with, the result would be undefined. In both cases, the undefined behaviour usually culminates in the application crashing unexpectedly.

Reference counting may therefore only be performed on objects which are explicitly allocated using `operator new()'. It is not possible to perform reference counting for objects which are members of an array, have been created on the stack, are global or static variables, or are member variables of other objects. There is no reliable way of programmatically determining if an object was allocated using `operator new()'. It is therefore your responsibility to ensure that reference counting is performed only on objects allocated using `operator new()'.

3 Internal Count

The first approach to implementing reference counting is to embed the count within the actual object. Member functions are then provided to allow parties to indicate that they are interested in, or no longer interested in the object. When the last party indicates they are no longer interested in the object, the object would delete itself. The OTC_Resource class encapsulates this functionality. To add the functionality to your class, your class should derive from the OTC_Resource class. If you know that your class will be used in a class hierarchy utilising multiple inheritance, and more than one class will be inherited from the OTC_Resource class, inheritance should be `virtual' to ensure that only one instance of the OTC_Resource class exists within the composite object.

The member function interface of the OTC_Resource class is given below.

  class OTC_Resource
{
public:

// = PROTOCOL

void reference() const;
// Increments by one the number of
// objects which reference this object.

void unReference() const;
// Decrements by one the number of
// objects which reference this object.
// If the count reaches <0> as a result,
// this object will be deleted.

// = QUERY

u_int numRefs() const;
// Returns a count of the number of
// reference to this object.
};
The protocol for using the class, is that when you become interested in the object, you should call the `reference()' member function for the object. When you are no longer interested in the object, the `unReference()' member function should be called. Calling `reference()' increments the reference count, partially ensuring that the object will not be deleted while you still have an interest in it. Calling `reference()' only partially ensures the object is not deleted, as a user of the object could ignore the protocol and delete the object explicitly. Provided that the protocol is not broken, you can safely use the object up till you call `unReference()'. You should not try to access the object after you have called `unReference()' as calling the function could have resulted in the object being destroyed.

A simple example of the protocol in action is given below.

  #include <OTC/refcnt/resource.hh>

class EX_Item : public OTC_Resource { };

EX_Item* item1 = new EX_Item;
EX_Item* item2 = new EX_Item;
EX_Item item3;
static EX_Item item4;

item1->reference();
item1->unReference(); // Item 1 deleted here.
delete item2; // Item 2 explicitly deleted here.
Note that once the `reference()' function has been called, you should not explicitly delete the object yourself. If you are passing the object to a function and you do not know if that function will call `reference()', you should call `reference()' yourself prior to calling the function and call `unReference()' once the function has returned.

Reference counting cannot be performed on objects which have not been explicitly created using `operator new()'. However, if you know that the `reference()' function will never be called for an instance of an object, it is still safe to create that object in another context such as in local or static scope. If `reference()' is never called for a object created using `operator new()', you must still delete it yourself.

To assist in tracking where reference counting is being performed on objects not allocated using `operator new()', run time warnings can be enabled. To enable these warnings you should set the environment variable OTCLIB_RESOURCEWARNINGS prior to executing your program. When a warning is generated, your program will not be terminated, but will continue to execute. Note that the mechanism used to determine if an object was allocated using `operator new()' is not always correct. Incorrect warnings may be generated where base classes implicitly or explicitly create from their constructors, objects derived from OTC_Resource.

4 Smart Pointers

Ensuring that the protocol of calling the functions `reference()' and `unReference()' is always adhered to, can be tedious and susceptible to error. One situation in which errors can easily arise, is where there are multiple exit points from a block of code and `unReference()' needs to be called at each of those points.

Manipulation of the reference count, through the `reference()' and `unReference()' functions is automated by the template class OTC_ResPtr. The OTC_ResPtr class is a form of smart pointer. You can use an instance of the class instead of a standard pointer when referring to an object. Upon being assigned an object, an instance of the smart pointer class will automatically invoke the `reference()' function for the object. When the smart pointer is destroyed, it will automatically call `unReference()' for the object it holds. Instances of the OTC_ResPtr class may also be assigned to each other.

As the OTC_ResPtr class is a template, you must parameterise it with the type of the object being pointed at. The template argument should not include `*'. The object to be held, must derive from the OTC_Resource class, or provide an equivalent interface to that of the OTC_Resource class. An example of using the OTC_ResPtr class is given below.

  #include <OTC/refcnt/resptr.hh>

OTC_ResPtr<EX_Item> item1 = new EX_Item;
OTC_ResPtr<EX_Item> item2;

item2 = item1;
item1 = new EX_Item;
item2 = 0; // First object created deleted.

// Second object created is deleted when <item1>
// goes out of scope.
To allow an instance of the smart pointer to be used in the same context as a standard pointer, the class provides a conversion operator to a standard pointer. The member function `operator->()' is also provided to allow pointer style access to data members and member functions of the object. For example:

  void process(EX_Item* theItem)
{
OTC_ResPtr<EX_Item> item = theItem;
// ...
}

OTC_ResPtr<EX_Item> item = new EX_Item;

foo->func();
process(foo);
As the reference count is part of the object, it is still safe to use the object independently of the OTC_ResPtr class. The OTC_ResPtr class only encapsulates the protocol for calling the `reference()' and `unReference()' functions.

With respect to normal pointers, the OTC_ResPtr class behaves much like a pointer to a non const object. If the behaviour you need is that of a pointer to a const object, you should use the OTC_CResPtr class. The OTC_ResPtr class derives from the OTC_CResPtr class, allowing the automatic conversion from the type OTC_ResPtr to OTC_CResPtr, much like a pointer to a non const object can be converted to a pointer to a const object.

5 External Count

To add reference counting to an object by deriving the class for that object from the OTC_Resource class, you need to have the source code for the class. If you do not have source code for the class, you will not be able to make such a change. One solution is to create a new class which derives from both the OTC_Resource class and the class for the object you wish to add reference counting. You would then use the new derived object where you would have originally used the base class object.

A second solution is not to add the reference count into the object, but maintain it separately to the object. One implementation of this approach, is provided in the form of the template classes OTC_CtrPtr and OTC_CtrVecPtr. The first class is used to manage single objects only. The second class is used for an array of objects. The classes may be used to refer to built-in types, as well as class objects. The classes are used in a manner similar to the OTC_ResPtr class. For example:

  #include <OTC/refcnt/ctrptr.hh>

OTC_CtrVecPtr<char> s1 = new char[10];
OTC_CtrVecPtr<char> s2 = s1;

s1 = 0;
s2 = 0; // Character array deleted.
As the reference count is not part of the object, you should not assign a pointer to an object directly to two separate instances of the smart pointer classes. For example, the following would result in the object being deleted twice.

  char* s0 = new char[10];

OTC_CtrVecPtr<char> s1 = s0;
OTC_CtrVecPtr<char> s2 = s0;

s1 = 0; // Character array deleted.
s2 = 0; // Character array deleted a second time.
When using the OTC_CtrPtr and OTC_CtrVecPtr classes, an instance of these class should be used as a handle when passing around the object. The handle can then be safely assigned to a further instance of the smart pointer class. For example:

  void process(OTC_CtrVecPtr<char>& s0)
{
OTC_CtrVecPtr<char> s1 = s0;
// ...
}

OTC_CtrVecPtr<char> s1 = new char[10];

process(s1);
s1 = 0; // Character array deleted.
Any objects referred to by instances of the OTC_CtrPtr and OTC_CtrVecPtr classes must have been originally created using `operator new()', and be able to be subsequently destroyed using `operator delete()'.

If using either of these classes to implement a delayed copy mechanism, the number of parties interested in an object can be determined using the `numRefs()' member function. Note that this is a member function of the smart pointer object, and not the object held. For example:

  class EX_Item1 {};

class EX_Item2
{
public:
void sync();

private:
OTC_CtrPtr<EX_Item1> myData;
};

void EX_Item2::sync()
{
if (myData.numRefs() > 1)
myData = new EX_Item1(*myData);
}
As with the OTC_ResPtr class, const versions of these classes exist. They are OTC_CCtrPtr and OTC_CCtrVecPtr.

6 Delayed Copying

One use of reference counting is a delayed copy mechanism. This is used in order to initially share data between objects when an object is created using another object, or an object is assigned to another object. A copy of the data is made only when an object needs to modify the data, and some other object has declared an interest in the data. Savings in memory use resulting from sharing the data rather than creating a copy each time can be considerable.

Reference counting can be used to implement a delayed copy mechanism. To do this, the count of the number of parties interested in an object is used to determine if a copy should be made of an object before it is modified. You can simplify this process by encapsulating the check into a single function, and calling that function prior to a change being made to the data. For example:

  void EX_Item2::sync()
{
if (myData->numRefs() > 1)
{
EX_Item1* tmpData = new EX_Item1(*myData);
tmpData->reference();
myData->unReference();
myData = tmpData;
}
}

void EX_Item2::modify()
{
sync();
// Modify <myData> here.
}
You will also need to manipulate the reference count in the constructor, destructor and assignment operator to adhere to the protocol for using the OTC_Resource class.

  class EX_Item1 : public OTC_Resource {} ;

EX_Item2::EX_Item2(EX_Item1* theData)
: myData(theData)
{
myData->reference();
}

EX_Item2::~EX_Item2()
{
myData->unReference();
}

EX_Item2& EX_Item2::operator=(EX_Item2 const& theItem)
{
if (&theItem != this)
{
if (myData != theItem.myData)
{
myData->unReference();
myData = theItem.myData;
myData->reference();
}
}
return *this;
}
The above example could be further simplified by using either of OTC_ResPtr, OTC_CtrPtr or OTC_CtrVecPtr as appropriate.