Home > Articles > Programming > C/C++

  • Print
  • + Share This

Shared Ownership

Is it always easy to find or designate a single owner for each resource in your program? Surprisingly, yes! And if you're having trouble finding one, you have very likely discovered a design problem in your software. Having said that, there are certain cases when sharing ownership may be the best or even the only option.

The responsibilities of sharing are divided between the objects being shared and their clients. A shared resource must keep a reference count of its owners. The owner, on the other hand, must notify the shared object when it releases it. The last owner to release the resource is also responsible for freeing it.

The simplest implementation of sharing is for the shared object to inherit the reference-counting behavior from the RefCounted class:

class RefCounted
{
public:
  RefCounted () : _count (1) {}
  int GetRefCount () const { return _count; }
  void IncRefCount () { _count++; }
  int DecRefCount () { return --_count; }
private
  int _count;
};

In terms of resource management, a reference count is a resource. If you acquire it, you should also release it. Once you realize this fact, the rest is easy. Just follow the first rule of acquisition—acquire the reference count in a constructor, release it in the destructor. There's even an equivalent of a smart pointer for sharing RefCounted objects:

template <class T>
class RefPtr
{
public:
  RefPtr (T * p) : _p (p) {}
  RefPtr (RefPtr<T> & p)
  {
    _p = p._p;
    _p->IncRefCount ();
  }
  ~RefPtr ()
  {
    if (_p->DecRefCount () == 0)
      delete _p;
  }
private
  T * _p;
};

Notice that class T in this template doesn't necessarily have to be a descendant of RefCounted, but it must have the methods IncRefCount and DecRefCount. Of course, a usable RefPtr should also have the standard overloads of the pointer-access operator. Adding transfer semantics to RefPtr is left as an exercise to the reader.

Ownership Networks

A linked list is an interesting case for resource management analysis. If you choose to make the list the principal owner of links, you end up implementing recursive ownership. Every link becomes the owner of its successor, and, transitively, the owner of the rest of the list. Here's the implementation of a link element using a smart pointer:

class Link
{
  // ...
private
  auto_ptr<Link> _next;
};

It's best to encapsulate link manipulation inside a list class that deals with resource transfers in and out of it.

What about a doubly linked list? The safest approach is to designate one direction, such as forward, as the "ownership" direction:

class DoubleLink
{
  // ...
private
  DoubleLink   *_prev;
  auto_ptr<DoubleLink> _next;
};

Be careful not to create circular linked lists.

Which brings us to an interesting topic—can resource management deal with circular ownership? Indeed it can, using a version of a mark-and-sweep algorithm. Here's an example of a template class that implements this approach:

template<class T>
class CyclPtr
{
public:
  CyclPtr (T * p)
    :_p (p), _isBeingDeleted (false)
  {}
  ~CyclPtr ()
  {
    _isBeingDeleted = true;
    if (!_p->IsBeingDeleted ())
      delete _p;

  }

  void Set (T * p)
  {
    _p = p;
  }
  bool IsBeingDeleted () const { return _isBeingDeleted; }
private
  T * _p;
  bool _isBeingDeleted;
};

Notice that we require class T to implement the IsBeingDeleted method, most likely by inheriting it from CyclPtr. Generalization to arbitrary ownership networks is straightforward.

  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.