Cross-Platform C++

ot
class ManagedObject

#include "ot/base/ManagedObject.h"

Common base class for all classes that rely on reference-counting to perform automatic object lifetime management. Object lifetime management (aka 'garbage collection') is the process of deciding when an object is no longer needed and destroying it when it is safe to do so.

Unlike many other languages, C++ does not offer any native object lifetime management facilities - the programmer is generally responsible for keeping track of when objects are created and destroyed.

While the manual approach to memory management is good and efficient for some systems, it can place an unnecessary burden on the programmer and some useful programming practices can become virtually impossible. A case in point is the idea of 'filters'. A filter class implements the same interface as another class, overriding some methods and delegating all other calls to a contained object (see FilterInputStream for an example). When using a manual approach, the user of a filter would have to decide who was responsible for the ownership of the contained object - the creator or the filter. Should the filter delete the object when it is itself destroyed? What if something else is relying on that object elsewhere in the program? These types of questions make systems development complicated. OpenTop employs a reference-counting scheme that allows the ownership of an object to be shared by all users of it - and the object is responsible for destroying itself when it is no longer referenced.

Much of the OpenTop library is inspired by the Java API (JDK). The Java API is considerably easier to use and richer than the equivalent C++ standard library. The designers of OpenTop believe that the JDK achieves much of its power and flexibility by being able to create objects on the fly in the knowledge that they will be destroyed when they are no longer used.


Reference-Counting

The ManagedObject class implements a reference-counting scheme similar to the scheme used by the Microsoft Compound Object Model (COM). Each object has a reference-count which is incremented each time a reference to the object is taken and decremented when the reference is no longer needed. At the point that the reference-count is finally decremented to zero the object will destroy itself.

Using reference-counting in this way makes the behaviour of a class deterministic. Resources are always freed at the point when the object is no longer referenced.

Just like in COM, this scheme puts a requirement on each client program to release references to objects that it has received. However, as OpenTop is written in C++, this burden is delegated to a template class called RefPtr<T> which takes care of most of the boiler plate code required to correctly perform object reference-counting.


Cyclic References

Simple reference-counting schemes generally work well, but they are subject to problems with cyclic references. A cycle occurs when two objects reference each other either directly or indirectly, with the result that neither object's reference-count will ever be decremented to zero. In this situation the objects in the cycle plus all those objects they reference will never be deleted.

There are various techniques that can be employed to prevent cyclic references. One effective technique is to establish the part of a class which is causing the dependency and factor that out into another class that both objects can reference. In this way we go from A<-->B which is cyclic to A-->C<--B which isn't.

Another possible technique involves the use of native pointers or references to classes that always have a lifetime that envelopes the referring object.

Whichever technique is chosen, it is important to monitor applications built using reference-counting to ensure that resource exhaustion does not occur due to cyclic references. It is a good idea to use a memory leak detector during development to help isolate these problems. When using Visual C++ on the Microsoft Windows platform, the MemCheckSystemMonitor class can be used to invoke the built-in memory-leak detection of the C run-time library.


Object Construction

When an instance is constructed its internal reference-count is set to zero. This is a deliberate choice so that objects created on the stack which never have another reference taken can go out of scope and be destroyed with a reference-count still equal to zero. This allows the implementation to test for erroneous destruction by ensuring that the reference-count is zero at destruction.

This scheme also allows derived class constructors to throw exceptions without leaving an invalid reference-count in the base object.

Please note, however, that great care should be taken when creating a ManagedObject on the stack. In particular the object should not be passed to another function that may increment the reference-count. This is because, when the reference-count is decremented, the object will attempt to destroy itself - which is not legal for stack-based objects. Unless you are absolutely sure about how the object will be used it is often preferable to avoid stack-based ManagedObjects altogether.


Reference-counting rules

There are a small number of rules that should be followed to ensure that ManagedObjects are used correctly. Note that implementing these rules is made much easier by using the RefPtr<> template class.

  1. Do not call addRef() or release() on objects created on the stack.
  2. Feel free to pass bare pointers to functions. Functions can use the passed pointer freely, including passing it to other functions. If it needs to store the pointer for use beyond the lifetime of the function, it must first increment the reference-count by calling addRef() (or store the pointer in a RefPtr<> variable). When writing functions that accept bare pointers, it should be noted that the reference-count of any objects passed to the function may be zero. This means that a single increment/decrement of the reference-count could result in the object's destruction. If the function passes the bare pointer to another function which may manipulate the objects reference-count, then it is recommended to take temporary ownership of the object by storing its pointer in a RefPtr<T> variable first.
  3. When returning a pointer from a function, (as a return value or a return parameter) the caller is responsible for incrementing (and decrementing) the reference-count. This behaviour mimics the behaviour of new(). It is strongly recommended when returning pointers from functions to use RefPtr<> in place of a bare pointer. This makes life easier for the caller because he is no longer responsible for calling addRef() and release() on the returned pointer. This also makes it possible to chain function calls together like this: url.openConnection()->getInputStream()->read();.

What should be a ManagedObject?

Reference-counting is not free, so ManagedObjects have a run-time and storage cost associated with them. For this reason some thought should be given to deciding what should be a ManagedObject.

When deciding whether or not a class should derive from ManagedObject, the following guidelines are useful:-

Factors indicating a preference towards ManagedObject:-

Factors indicating a preference towards a common class or struct:-

Multi-threaded Applications

When compiled with the OT_MT flag defined, incrementing and decrementing the reference-count is guaranteed to be performed in a thread-safe, atomic fashion. This means that reference-counting will work as advertised, even when sharing ManagedObjects between multiple threads.

This does not mean, however, that classes derived from ManagedObject are automatically thread-safe. In order to be safely used by multiple threads, derived classes will have to ensure that they protect their internal state from the potential memory corruption that can result from conflicting concurrent access.

In general, with the exception of the reference-count, classes within the OpenTop library do not protect their internal state from concurrent multi-threaded access.

See also:
RefPtr<>



Constructor/Destructor Summary
ManagedObject()
         Default constructor.
ManagedObject(const ManagedObject& rhs)
         Copy constructor.
~ManagedObject()
         Destructor.

Method Summary
 void addRef()
         Increments the reference-count of the object.
 unsigned long getRefCount() const
         Returns the current reference-count of the object.
 virtual void onFinalRelease()
         Virtual method called when the object's reference-count has been decremented to zero.
 ManagedObject& operator=(const ManagedObject& rhs)
         Assignment operator.
 void release()
         Decrements the reference-count of the object.

Constructor/Destructor Detail

ManagedObject

 ManagedObject()
Default constructor. Initializes the reference-count to zero.


ManagedObject

 ManagedObject(const ManagedObject& rhs)
Copy constructor. A compiler-generated copy constructor would be unsuitable because the reference-count for a new object must be initialized to zero.

Parameters:
rhs - ManagedObject being copied.

~ManagedObject

virtual ~ManagedObject()
Destructor. In the debug build, an assertion is made that the reference-count is zero. This is done to trap programming errors where the reference-counting rules have not been correctly followed.


Method Detail

addRef

void addRef()
Increments the reference-count of the object. In multi-threaded versions of the library, the reference-count is incremented using an atomic, thread-safe operation.

In the debug build, an assertion is made that the reference-count is not equal to zero after it has been incremented. This check can help to trap errors where an object has been erroneously deleted.

See also:
release()

getRefCount

unsigned long getRefCount() const
Returns the current reference-count of the object.

Returns:
the current reference-count.

onFinalRelease

virtual void onFinalRelease()
Virtual method called when the object's reference-count has been decremented to zero. A zero reference-count indicates that the object is no longer needed and should be deleted. The default implementation calls delete @ this.

Only override this method if you need to perform some other custom garbage collection.


operator=

ManagedObject& operator=(const ManagedObject& rhs)
Assignment operator. A compiler-generated assignment operator would be unsuitable because the reference-count for an object must remain unchanged.

Parameters:
rhs - ManagedObject being copied.

release

void release()
Decrements the reference-count of the object. When the reference-count becomes zero, the virtual function onFinalRelease() is called which will normally delete the object.

In multi-threaded versions of the library, the reference-count is decremented using an atomic, thread-safe operation.

See also:
addRef()


Cross-Platform C++

Found a bug or missing feature? Please email us at support@elcel.com

Copyright © 2000-2003 ElCel Technology   Trademark Acknowledgements