Logo Search packages:      
Sourcecode: efax-gtk version File versions

shared_handle.h

/* Copyright (C) 2004 Chris Vine

This program is distributed under the General Public Licence, version 2.
For particulars of this and relevant disclaimers see the file
COPYING distributed with the source files.

*/

#ifndef SHARED_HANDLE_H
#define SHARED_HANDLE_H

#include <cstdlib>
#include <glibmm/thread.h>
#include <glib/gmem.h>

/*
The Shared_handle class is similar to the Shared_ptr class (it keeps a
reference count and deletes the handled object when the count reaches
0), but it does not have pointer semantics.  Accordingly, it can be
used to manage the memory of arrays and other objects allocated on the
heap.

Because it is useful with arrays, by default it deallocates memory
using C++ delete[].  However, if a Shared_handle object is passed a
function object type as a second template argument when instantiated,
it will use that function object to delete memory.  This enables it to
handle the memory of any object, such as objects to be deleted using
std::free() or Glib's g_free() and g_slist_free().  Note that the
deallocator must do nothing if the value of the pointer passed to it
is NULL, so if it does not do this by default a NULL condition must be
tested for (std::free(), std::delete[] and Glib's g_free() do this
test themselves).

To reflect the fact that it is just a handle for a pointer, it has
different instantiation semantics from a Shared_ptr object.  A
Shared_ptr object is instantiated using this syntax:

  Shared_ptr<Obj> sh_ptr(new Obj);

A Shared_handle is instantiated using this syntax (note that the
instantiated handle is for type Obj* and not Obj):

  Shared_handle<Obj*> sh_handle(new Obj[n]);

The only method to obtain the underlying pointer is the get() method.
Thus, if the object referenced is an array allocated on the heap, to
use indexing you would do this:

  Shared_handle<char*> handle(new char[10]);
  handle.get()[0] = 'a';
  std::cout << handle.get()[0] << std::endl;

There is also a Scoped_handle class below, which deletes its object as
soon as it goes out of scope.  It can be viewed as a Shared_handle
which cannot be assigned to or used as the argument to a copy
constructor and therefore which cannot have a reference count of more
than 1. It is used where, if you wanted pointer semantics, you might
use a const std::autoptr<>.

*/

/********************* here are some deleter classes *******************/

template <class T> class Standard_array_delete {
public:
  void operator()(T obj_p) {
    delete[] obj_p;
  }
};

class C_free {
public:
  void operator()(void* obj_p) {
    std::free(obj_p);
  }
};

class G_free {
public:
  void operator()(gpointer obj_p) {
    g_free(obj_p);
  }
};

/********************* define some typedefs for Glib ******************/

template <class T, class Dealloc> class Shared_handle;
template <class T, class Dealloc> class Scoped_handle;

typedef Shared_handle<gchar*, G_free> Gchar_shared_handle;
typedef Scoped_handle<gchar*, G_free> Gchar_scoped_handle;


/******************* now the handle class definitions *****************/

template <class T, class Dealloc = Standard_array_delete<T> > class Shared_handle {

  Dealloc deleter;

  struct {
    unsigned int* ref_count_p;
    void* obj_p;
  } ref_items;

  void unreference(void) {
    --(*ref_items.ref_count_p);
    if (*ref_items.ref_count_p == 0) {
      deleter(static_cast<T>(ref_items.obj_p));
      delete ref_items.ref_count_p;
    }
  }

  void reference(void) {
    ++(*ref_items.ref_count_p);
  }

public:
  // constructor of first Shared_handle holding the referenced object
  explicit Shared_handle(T ptr = 0) {
    ref_items.ref_count_p = new unsigned int(1);
    ref_items.obj_p = ptr;
  }

  // copy constructor
  Shared_handle(const Shared_handle& sh_ptr) throw() {
    ref_items = sh_ptr.ref_items;
    reference();
  }

  // copy assignment
  Shared_handle& operator=(const Shared_handle& sh_ptr) throw() {

    // check whether we are already referencing this object -
    // if so make this a null op.  This will also deal with
    // self-assignment
    if (ref_items.obj_p != sh_ptr.ref_items.obj_p) {

      // first unreference any object referenced by this shared pointer
      unreference();

      // now inherit the ref_items structure from the assigning
      // shared pointer and reference the object it references
      ref_items = sh_ptr.ref_items;
      reference();
    }
    return *this;
  }

  T get(void) const throw() {return static_cast<T>(ref_items.obj_p);}

  unsigned int get_refcount(void) const throw() {return *ref_items.ref_count_p;}

  // destructor
  ~Shared_handle(void) throw() {unreference();}
};

template <class T, class Dealloc = Standard_array_delete<T> > class Scoped_handle {

  Dealloc deleter;
  void* obj_p;

  // private copy constructor and assignment operator -
  // scoped handle cannot be copied
  Scoped_handle(const Scoped_handle&) throw();
  void operator=(const Scoped_handle&) throw();
public:
  // constructor
  explicit Scoped_handle(T ptr): obj_p(ptr) {;}

  T get(void) const throw() {return static_cast<T>(obj_p);}

  // destructor
  ~Scoped_handle(void) throw() {deleter(static_cast<T>(obj_p));}
};


/*
  Class Shared_lock_handle is a version of the shared handle class
  which includes mutex locking so that it can be accessed in multiple
  threads. Note that only the reference count is protected by a mutex,
  so this is thread safe in the sense in which a raw pointer is thread
  safe.  A shared handle accessed in one thread referencing a
  particular object is thread safe as against another shared handle
  accessing the same object in a different thread.  It is thus
  suitable for use in different Std C++ containers which exist in
  different threads but which contain shared objects by reference.
  But:

  1.  If the referenced object is to be modified in one thread and
      read or modified in another thread an appropriate mutex for the
      referenced object is required (unless that referenced object
      does its own locking).

  2.  If the same instance of shared handle is to be modified in one
      thread (by assigning to the handle so that it references a
      different object), and copied (assigned from or used as the
      argument of a copy constructor) or modified in another thread, a
      mutex for that instance of shared handle is required.

  3.  Objects referenced by shared handles which are objects for
      which POSIX provides no guarantees (in the main, those which are
      not built-in types), such as strings and similar containers, may
      not support concurrent reads in different threads.  That depends
      on the library implementation concerned.  If that is the case, a
      mutex for the referenced object will also be required when
      reading any given instance of such an object in more than one
      thread by dereferencing any shared handles referencing it (and
      indeed, when not using shared handles at all).
*/

template <class T, class Dealloc = Standard_array_delete<T> > class Shared_lock_handle {

  Dealloc deleter;

  struct Ref_items {
    Glib::Mutex* mutex_p;
    unsigned int* ref_count_p;
    void* obj_p;
  } ref_items;

  // Shared_lock_handle<T, Dealloc>::unreference() does not throw exceptions
  // because  Glib::Mutex::~Mutex(), Glib::Mutex::lock() and Glib::Mutex::unlock()
  // do not throw
  void unreference(void) {
    ref_items.mutex_p->lock();
    --(*ref_items.ref_count_p);
    if (*ref_items.ref_count_p == 0) {
      deleter(static_cast<T>(ref_items.obj_p));
      delete ref_items.ref_count_p;
      ref_items.mutex_p->unlock();
      delete ref_items.mutex_p;
    }
    else ref_items.mutex_p->unlock();
  }
  
  // Shared_lock_handle<T, Dealloc>::reference() does not throw exceptions because
  // Glib::Mutex::Lock::Lock() and Glib::Mutex:::Lock::~Lock() do not throw
  void reference(void) {
    Glib::Mutex::Lock lock(*ref_items.mutex_p);
    ++(*ref_items.ref_count_p);
  }

public:
  // constructor of first Shared_lock_handle holding the referenced object
  explicit Shared_lock_handle(T ptr = 0) {
    ref_items.mutex_p = new Glib::Mutex;
    // make this constructor exception safe
    try {
      ref_items.ref_count_p = new unsigned int(1);
    }
    catch (...) {
      delete ref_items.mutex_p;
      throw;
    }
    ref_items.obj_p = ptr;
  }

  // copy constructor
  Shared_lock_handle(const Shared_lock_handle& sh_ptr) throw() {
    ref_items = sh_ptr.ref_items;
    reference();
  }

  // copy assignment
  Shared_lock_handle& operator=(const Shared_lock_handle& sh_ptr) throw() {

    // check whether we are already referencing this object -
    // if so make this a null op.  This will also deal with
    // self-assignment
    if (ref_items.obj_p != sh_ptr.ref_items.obj_p) {

      // first unreference any object referenced by this shared handle
      unreference();
      
      // now inherit the ref_items structure from the assigning
      // shared handle and reference the object it references
      ref_items = sh_ptr.ref_items;
      reference();
    }
    return *this;
  }

  T get(void) const throw() {return static_cast<T>(ref_items.obj_p);}

  unsigned int get_refcount(void) const throw() {
    Glib::Mutex::Lock lock(*ref_items.mutex_p);
    return *ref_items.ref_count_p;
  }

  // destructor
  ~Shared_lock_handle(void) throw() {unreference();}
};

#endif

Generated by  Doxygen 1.6.0   Back to index