Uncounted pointers in C++

Sometimes I comes across an idea that strikes me as
"interesting, but is there really a use for it". Less
frequently, I subsequently finds so many uses for it that
I can't imagine how I ever survived without. I first
encountered the idea of "weak references" in discussions
of Java garbage collection. Since then I've found several
applications for the idea in C++.

The idea of a "reference" is much more general than the
C++ language element of the same name. The broader
definition used in design, garbage collection, and other
programming languages includes any mechanism for
referring to an object. In C++ this would include
pointers (both base types and the various types of "smart
pointer"). It is this more general definition that I use
in this article.

"Garbage collection" is a tool for managing the memory
referred to by references - it works on the principle
that when there are no active references to an object
then the program can never use the object again and the
memory allocated to it may be reclaimed. This works well
in languages like Java that have support for references
in the language. (In Java all objects are managed by
references.)

Not all references need be equal, and two of the
variations that have been developed are "strong
references" and "weak references". The garbage collection
tool only considers strong references when deciding if an
object may be "collected". Any weak references to an
object will become "null" if the object is collected.
There are other variations, for example, in Java there
are now three reference classes WeakReference,
SoftReference and PhantomReference. These are essentially
hooks into the Java garbage collection mechanism with
different collection semantics.

"smart" pointers

For a variety of reasons garbage collection is not
standard in C++ and in its most general form would be
impractical to implement. For instance, C++ allows one to
encrypt a pointer (e.g. by xoring it with a password), to
write it out to a file, destroy all trace of the
original, reload the encrypted version, decrypt it and
regain access to the original object.

Instead of garbage collection C++ programs use a variety
of memory management techniques. One of the most
effective of these is the use of a "smart pointers" -
classes that emulate a pointer and know when an object is
to be delete. The standard library contains only one
design of smart pointer - the auto_ptr template but there
are many other possibilities. (For some of the
possibilities check out http://www.boost.org/ and
"arglib" on my website at
http://www.octopull.co.uk/.)

The idea of strong and weak references inspired the
counted_ptr and uncounted_ptr templates in my "arglib"
library. (We'll deal with uncounted_ptr shortly, after
discussing counted_ptr.)
The strong reference implementation is counted_ptr and
works by keeping count of the number of references to an
object so that it may be deleted when the count reaches
zero. (There is another implementation of this same idea
on the "boost" website as "shared_ptr".)

The problem with counted_ptr

As proponents of garbage collection will be quick to
point out - there are some problems with this form of
reference counting. If you've used a version of
counted_ptr or any other kind of reference counting
pointer then you will know the main issue is that of
"loops".

A loop occurs when two (or more) objects - such as
"Pippa" and "Alan" in Example 1 - contain a cycle of
references to each other. In the example "Pippa" is never
deleted because of the reference in "Alan", but "Alan" is
never deleted because of the reference in "Pippa".
Two other things you will notice in the example are parts
of a test harness framework: the macro ARG_TEST and the
class instance_count. ARG_TEST logs successful tests or
reports unsuccessful ones, instance_count is a utility
class that counts the instances in existence.

Before encountering the idea of weak references and
developing explicit support for them I used a mix of
smart and raw pointers (with an often arbitrary choice of
direction for the "ownership"). However, as Example 2
illustrates, this leads to the need for Pippa (say) to
notify Alan that she's being deleted.

Even in our simple example this is ugly. In more general
usage this can lead to a need to maintain a list of
objects to notify and the complexity of maintaining it.
Other designs, such as external association lists may be
more manageable, but this is still a far cry from the
effortlessness we might expect from smart pointers.

uncounted_ptr<>

An uncounted_ptr refers to the same object as a
counted_ptr but isn't included in the reference count.
This makes it the weak reference type corresponding to
counted_ptr's strong reference. When the object is
deleted (because there are no more counted_ptrs referring
to it) the uncounted_ptr becomes 0.

With uncounted_ptr in our toolkit we can now revisit the
example. The loop problem is solvable by changing the
link implementation between "man" and "woman" to
uncounted_ptr. This breaks the loop by allowing "Pippa"
to be deleted in Example 3. The example also illustrates
that there is no "dangling reference" left in "Alan" - an
ever-present danger when using "raw" pointers.

using "smart pointers"

There are a few points to note about any "smart pointers"
you may be using. Most importantly, you need to
understand the ownership regime that they implement. Most
smart pointers, like the standard auto_ptr, take
ownership of an object, so initialising two auto_ptrs
from a single raw pointer value will lead to problems.
(Because both pointers are entitled to delete the object,
but this will lead to "undefined behavior".)

Like auto_ptr, counted_ptr assumes ownership of the
referenced object. Unlike auto_ptr it shares (not
transfers) the ownership by means of the copy
construction assignment operations. This gives
counted_ptr the require "value semantics" needed to be
used successfully in standard library containers.

one or more counted_ptrs, but it does not share in the
ownership of the object. An uncounted_ptr automatically
becomes 0 when the object is deleted. (Note however, that
dereferencing an uncounted_ptr does not attempt to guarantee
preserve the object long enough to complete the current expression -
if you wish to do this then create a temporary counted_ptr.)