/* Copyright (C) 2004 to 2014 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License, version 2.1, for more details.

   You should have received a copy of the GNU Lesser General Public
   License, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License.  With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence.  This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).

*/

#ifndef CGU_SHARED_PTR_H
#define CGU_SHARED_PTR_H

// define this if, instead of GLIB atomic funcions/memory barriers,
// you want to use a (slower) mutex to lock the reference count in the
// SharedLockPtr class
/* #define CGU_SHARED_LOCK_PTR_USE_MUTEX 1 */

#include <utility>    // for std::move and std::swap
#include <exception>
#include <new>
#include <functional> // for std::less and std::hash<T*>
#include <cstddef>    // for std::size_t

#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
#include <c++-gtk-utils/mutex.h>
#else
#include <glib.h>
#endif

#include <c++-gtk-utils/cgu_config.h>

/**
 * @addtogroup handles handles and smart pointers
 */

namespace Cgu {

/**
 * @class SharedPtrError shared_ptr.h c++-gtk-utils/shared_ptr.h
 * @brief This is an exception struct thrown as an alternative to
 * deleting a managed object when internal memory allocation for
 * SharedPtr or SharedLockPtr fails in their reset() method or in
 * their constructor which takes a pointer.
 * @ingroup handles
 * @sa SharedPtr SharedLockPtr SharedPtrAllocFail
 *
 * This is an exception struct thrown as an alternative to deleting a
 * managed object when SharedPtr<T>::SharedPtr(T*),
 * SharedLockPtr<T>::SharedLockPtr(T*), SharedPtr<T>::reset(T*) or
 * SharedLockPtr<T>::reset(T*), would otherwise throw std::bad_alloc.
 * To make those methods do that, Cgu::SharedPtrAllocFail::leave is
 * passed as their second argument.
 *
 * If the exception is thrown, the struct has a member 'obj' of type
 * T*, which is a pointer to the object originally passed to those
 * methods, so the user can deal with it appropriately.  This enables
 * the result of the new expression to be passed directly as the
 * argument to those methods without giving rise to a resource leak,
 * as in:
 * 
 * @code
 * using namespace Cgu;
 * SharedPtr<T> s;                              // doesn't throw
 * try {
 *   s.reset(new T, SharedPtrAllocFail::leave); // both T allocation and reset() might throw
 * }
 * catch (std::bad_alloc&) {
 *   ...
 * }
 * catch (SharedPtrError<T>& e) {
 *   e.obj->do_something();
 *   ...
 * }
 * ...
 * @endcode 
 *
 * As above, a catch block will need to deal with std::bad_alloc (if
 * the call to the new expression when creating the T object fails)
 * as well as SharedPtrError (if the call to the new expression in
 * the reset() method fails after a valid T object has been
 * constructed).
 */

template <class T> struct SharedPtrError: public std::exception {
  T* obj;
  virtual const char* what() const throw() {return "SharedPtrError\n";}
  SharedPtrError(T* p): obj(p) {}
};

/**
 * enum Cgu::SharedPtrAllocFail::Leave
 * The enumerator Cgu::SharedPtrAllocFail::leave is passed as the
 * second argument of the reset() method of SharedPtr or
 * SharedLockPtr, or in their constructor which takes a pointer, in
 * order to prevent the method deleting the object passed to it if
 * reset() fails internally because of memory exhaustion.
 * @ingroup handles
 */
namespace SharedPtrAllocFail {
 enum Leave {leave};
}


/**
 * @class SharedPtr shared_ptr.h c++-gtk-utils/shared_ptr.h
 * @brief This is a smart pointer for managing the lifetime of objects
 * allocated on freestore.
 * @ingroup handles
 * @sa SharedLockPtr SharedPtrError
 *
 * This is a smart pointer for managing the lifetime of objects
 * allocated on freestore with the new expression.  A managed object
 * will be deleted when the last SharedPtr referencing it is
 * destroyed.
 *
 * @b Comparison @b with @b std::shared_ptr
 *
 * Most of the things that can be done by this class can be done by
 * using std::shared_ptr from C++11/14, but this class is retained in
 * the c++-gtk-utils library not only to retain compatibility with
 * series 1.2 of the library, but also to cater for some cases not met
 * (or not so easily met) by std::shared_ptr:
 *
 * 1. Glib memory slices provide an efficient small object allocator
 *    (they are likely to be significantly more efficient than global
 *    operator new()/new[](), which generally hand off to malloc(),
 *    and whilst malloc() is good for large block allocations it is
 *    generally poor as a small object allocator).  Internal
 *    Cgu::SharedPtr allocation using glib memory slices can be
 *    achieved by compiling the library with the
 *    \--with-glib-memory-slices-no-compat configuration option.
 * 2. If glib memory slices are not used (which do not throw),
 *    constructing a shared pointer for a new managed object (or
 *    calling reset() for a new managed object) might throw if
 *    internal allocation fails.  Although by default the
 *    Cgu::SharedPtr implementation will delete the new managed object
 *    in such a case, it also provides an alternative constructor and
 *    reset() method which instead enable the new object to be
 *    accessed via the thrown exception object so that user code can
 *    decide what to do; std::shared_ptr deletes the new object in
 *    every case.
 * 3. A user can explicitly state whether the shared pointer object is
 *    to have atomic increment and decrement-and-test with respect to
 *    the reference count so that the reference count is thread safe
 *    ('no' in the case of Cgu::SharedPtr, and 'yes' in the case of
 *    Cgu::SharedLockPtr).  Using atomic functions is unnecessary if
 *    the managed object concerned is only addressed in one thread
 *    (and might cause unwanted cache flushing in certain
 *    circumstances).  std::shared_ptr will generally always use
 *    atomic functions with respect to its reference count in a
 *    multi-threaded program.
 *
 * In favour of std::shared_ptr, it has an associated
 * std::make_shared() factory function which will construct both the
 * referenced object and the shared pointer's reference count within a
 * single memory block when the first shared pointer managing a
 * particular object is constructed.  Cgu::SharedPtr and
 * Cgu::SharedLockPtr always allocate these separately, but this is
 * partly mitigated by the use of glib memory slices to allocate the
 * reference count where the \--with-glib-memory-slices-no-compat
 * configuration option is chosen.
 *
 * In addition, std::shared_ptr has an associated std::weak_ptr class,
 * which Cgu::SharedPtr does not (there is a Cgu::GobjWeakHandle
 * class, but that is cognate with Cgu::GobjHandle and is only usable
 * with GObjects).
 *
 * If the library is compiled with the
 * \--with-glib-memory-slices-no-compat configuration option, as
 * mentioned Cgu::SharedPtr constructs its reference counting
 * internals using glib memory slices.  Although it is safe in a
 * multi-threaded program if glib < 2.32 is installed to construct a
 * static SharedPtr object in global namespace (that is, prior to
 * g_thread_init() being called) by means of the default constructor
 * and/or a pointer argument of NULL, it is not safe if constructed
 * with a non-NULL pointer value.  If glib >= 2.32 is installed,
 * global objects with memory slices are safe in all
 * circumstances. (Having said that, it would be highly unusual to
 * have global SharedPtr objects.)
 */

template <class T> class SharedPtr {

#ifndef DOXYGEN_PARSING
  struct RefItems {
    unsigned int* ref_count_p;
    T* obj_p;
  } ref_items;
#endif

  void unreference() {
    if (!ref_items.ref_count_p) return;
    --(*ref_items.ref_count_p);
    if (*ref_items.ref_count_p == 0) {
#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      g_slice_free(unsigned int, ref_items.ref_count_p);
#else
      delete ref_items.ref_count_p;
#endif
      delete ref_items.obj_p;
    }
  }

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

public:
/**
 * Constructor taking an unmanaged object.
 * @param ptr The object which the SharedPtr is to manage (if any).
 * @exception std::bad_alloc This constructor will not throw if the
 * 'ptr' argument has a NULL value (the default), otherwise it might
 * throw std::bad_alloc if memory is exhausted and the system throws
 * in that case.  If such an exception is thrown, this constructor is
 * exception safe (it does not leak resources), but as well as
 * cleaning itself up this constructor will also delete the managed
 * object passed to it to avoid a memory leak.  If such automatic
 * deletion is not wanted in that case, use the version of this
 * constructor taking a Cgu::SharedPtrAllocFail::Leave tag argument.
 * @note std::bad_alloc will not be thrown if the library has been
 * installed using the \--with-glib-memory-slices-no-compat
 * configuration option: instead glib will terminate the program if it
 * is unable to obtain memory from the operating system.
 */
  explicit SharedPtr(T* ptr = 0) {

    if ((ref_items.obj_p = ptr)) { // not NULL
#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(unsigned int);
      *ref_items.ref_count_p = 1;
#else
      try {
	ref_items.ref_count_p = new unsigned int(1);
      }
      catch (...) {
	delete ptr;  // if allocating the int referenced by ref_items.ref_count_p
                     // has failed then delete the object to be referenced to
                     // avoid a memory leak
	throw;
      }
#endif
    }
    else ref_items.ref_count_p = 0;
  }

/**
 * Constructor taking an unmanaged object.
 * @param ptr The object which the SharedPtr is to manage.
 * @param tag Passing the tag emumerator
 * Cgu::SharedPtrAllocFail::leave causes this constructor not to
 * delete the new managed object passed as the 'ptr' argument in the
 * event of internal allocation in this method failing because of
 * memory exhaustion (in that event, Cgu::SharedPtrError will be
 * thrown).
 * @exception Cgu::SharedPtrError This constructor might throw
 * Cgu::SharedPtrError if memory is exhausted and the system would
 * otherwise throw std::bad_alloc in that case.  This constructor is
 * exception safe (it does not leak resources), and if such an
 * exception is thrown it will clean itself up, but it will not
 * attempt to delete the new managed object passed to it.  Access to
 * the object passed to the 'ptr' argument can be obtained via the
 * thrown Cgu::SharedPtrError object.
 * @note 1. On systems with over-commit/lazy-commit combined with
 * virtual memory (swap), it is rarely useful to check for memory
 * exhaustion, so in those cases this version of the constructor will
 * not be useful.
 * @note 2. If the library has been installed using the
 * \--with-glib-memory-slices-no-compat configuration option this
 * version of the constructor will also not be useful: instead glib
 * will terminate the program if it is unable to obtain memory from
 * the operating system.
 */
  SharedPtr(T* ptr, Cgu::SharedPtrAllocFail::Leave tag) {

    if ((ref_items.obj_p = ptr)) { // not NULL
#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(unsigned int);
      *ref_items.ref_count_p = 1;
#else
      try {
	ref_items.ref_count_p = new unsigned int(1);
      }
      catch (std::bad_alloc&) { // as we are not rethrowing, make NPTL friendly
	throw SharedPtrError<T>(ptr);
      }
#endif
    }
    else ref_items.ref_count_p = 0;
  }

/**
 * Causes the SharedPtr to cease to manage its managed object (if
 * any), deleting it if this is the last SharedPtr object managing it.
 * If the argument passed is not NULL, the SharedPtr object will
 * manage the new object passed (which must not be managed by any
 * other SharedPtr object).  This method is exception safe, but see
 * the comments below on std::bad_alloc.
 * @param ptr NULL (the default), or a new unmanaged object to manage.
 * @exception std::bad_alloc This method will not throw if the 'ptr'
 * argument has a NULL value (the default) and the destructor of a
 * managed object does not throw, otherwise it might throw
 * std::bad_alloc if memory is exhausted and the system throws in that
 * case.  Note that if such an exception is thrown then this method
 * will do nothing (it is strongly exception safe and will continue to
 * manage the object it was managing prior to the call), except that
 * it will delete the new managed object passed to it to avoid a
 * memory leak.  If such automatic deletion in the event of such an
 * exception is not wanted, use the reset() method taking a
 * Cgu::SharedPtrAllocFail::Leave tag type as its second argument.
 * @note std::bad_alloc will not be thrown if the library has been
 * installed using the \--with-glib-memory-slices-no-compat
 * configuration option: instead glib will terminate the program if it
 * is unable to obtain memory from the operating system.
 */
  void reset(T* ptr = 0) {
    SharedPtr tmp(ptr);
    std::swap(ref_items, tmp.ref_items);
  }

/**
 * Causes the SharedPtr to cease to manage its managed object (if
 * any), deleting it if this is the last SharedPtr object managing it.
 * The SharedPtr object will manage the new object passed (which must
 * not be managed by any other SharedPtr object).  This method is
 * exception safe, but see the comments below on Cgu::SharedPtrError.
 * @param ptr A new unmanaged object to manage (if no new object is to
 * be managed, use the version of reset() taking a default value of
 * NULL).
 * @param tag Passing the tag emumerator
 * Cgu::SharedPtrAllocFail::leave causes this method not to delete the
 * new managed object passed as the 'ptr' argument in the event of
 * internal allocation in this method failing because of memory
 * exhaustion (in that event, Cgu::SharedPtrError will be thrown).
 * @exception Cgu::SharedPtrError This method might throw
 * Cgu::SharedPtrError if memory is exhausted and the system would
 * otherwise throw std::bad_alloc in that case.  Note that if such an
 * exception is thrown then this method will do nothing (it is
 * strongly exception safe and will continue to manage the object it
 * was managing prior to the call), and it will not attempt to delete
 * the new managed object passed to it.  Access to the object passed
 * to the 'ptr' argument can be obtained via the thrown
 * Cgu::SharedPtrError object.
 * @note 1. On systems with over-commit/lazy-commit combined with
 * virtual memory (swap), it is rarely useful to check for memory
 * exhaustion, so in those cases this version of the reset() method
 * will not be useful.
 * @note 2. If the library has been installed using the
 * \--with-glib-memory-slices-no-compat configuration option this
 * version of the reset() method will also not be useful: instead glib
 * will terminate the program if it is unable to obtain memory from
 * the operating system.
 */
  void reset(T* ptr, Cgu::SharedPtrAllocFail::Leave tag) {
    SharedPtr tmp(ptr, tag);
    std::swap(ref_items, tmp.ref_items);
  }

 /**
  * This copy constructor does not throw.
  * @param sh_ptr The shared pointer to be copied.
  */
  SharedPtr(const SharedPtr& sh_ptr) {
    ref_items = sh_ptr.ref_items;
    reference();
  }

 /**
  * The move constructor does not throw.  It has move semantics.
  * @param sh_ptr The shared pointer to be moved.
  */
  SharedPtr(SharedPtr&& sh_ptr) {
    ref_items = sh_ptr.ref_items;
    sh_ptr.ref_items.ref_count_p = 0;
    sh_ptr.ref_items.obj_p = 0;
  }

  template <class U> friend class SharedPtr;

 /**
  * A version of the copy constructor which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * copy constructor does not throw.
  * @param sh_ptr The shared pointer to be copied.
  */
  template <class U> SharedPtr(const SharedPtr<U>& sh_ptr) {
    // because we are allowing an implicit cast from derived to
    // base class referenced object, we need to assign from each
    // member of sh_ptr.ref_items separately
    ref_items.ref_count_p = sh_ptr.ref_items.ref_count_p;
    ref_items.obj_p = sh_ptr.ref_items.obj_p;
    reference();
  }

 /**
  * A version of the move constructor which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * move constructor does not throw.
  * @param sh_ptr The shared pointer to be moved.
  */
  template <class U> SharedPtr(SharedPtr<U>&& sh_ptr) {
    // because we are allowing an implicit cast from derived to
    // base class referenced object, we need to assign from each
    // member of sh_ptr.ref_items separately
    ref_items.ref_count_p = sh_ptr.ref_items.ref_count_p;
    ref_items.obj_p = sh_ptr.ref_items.obj_p;
    sh_ptr.ref_items.ref_count_p = 0;
    sh_ptr.ref_items.obj_p = 0;
  }

 /**
  * This method (and so copy or move assignment) does not throw unless
  * the destructor of a managed object throws.
  * @param sh_ptr the assignor.
  * @return The SharedPtr object after assignment.
  */
  // having a value type as the argument, rather than reference to const
  // and then initialising a tmp object, gives the compiler more scope
  // for optimisation, and also caters for r-values without a separate
  // overload
  SharedPtr& operator=(SharedPtr sh_ptr) {
    std::swap(ref_items, sh_ptr.ref_items);
    return *this;
  }

 /**
  * A version of the assignment operator which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * method does not throw unless the destructor of a managed object
  * throws.
  * @param sh_ptr the assignor.
  * @return The SharedPtr object after assignment.
  */
  template <class U> SharedPtr& operator=(const SharedPtr<U>& sh_ptr) {
    return operator=(SharedPtr(sh_ptr));
  }

 /**
  * A version of the operator for move assignment which enables
  * pointer type conversion (assuming the type passed is implicitly
  * type convertible to the managed type, such as a derived type).
  * This method does not throw unless the destructor of a managed
  * object throws.
  * @param sh_ptr the shared pointer to be moved.
  * @return The SharedPtr object after the move operation.
  */
  template <class U> SharedPtr& operator=(SharedPtr<U>&& sh_ptr) {
    return operator=(SharedPtr(std::move(sh_ptr)));
  }

 /**
  * This method does not throw.
  * @return A pointer to the managed object (or NULL if none is
  * managed).
  */
  T* get() const {return ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return A reference to the managed object.
  */
  T& operator*() const {return *ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return A pointer to the managed object  (or NULL if none is
  * managed).
  */
  T* operator->() const {return ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return The number of SharedPtr objects referencing the managed
  * object (or 0 if none is managed by this SharedPtr).
  */
  unsigned int get_refcount() const {return (ref_items.ref_count_p) ? *ref_items.ref_count_p : 0;}

 /**
  * The destructor does not throw unless the destructor of a managed
  * object throws - that should never happen.
  */
  ~SharedPtr() {unreference();}
};

/**
 * @class SharedLockPtr shared_ptr.h c++-gtk-utils/shared_ptr.h
 * @brief This is a smart pointer for managing the lifetime of objects
 * allocated on freestore, with a thread safe reference count.
 * @ingroup handles
 * @sa SharedPtr SharedPtrError
 *
 * Class SharedLockPtr is a version of the shared pointer class which
 * includes synchronization so that it can handle objects accessed in
 * multiple threads (although the word Lock is in the title, by
 * default it uses glib atomic functions to access the reference count
 * rather than a mutex, so the overhead should be very small).  Note
 * that only the reference count is protected, so this is thread safe
 * in the sense in which a raw pointer is thread safe.  A shared
 * pointer accessed in one thread referencing a particular object is
 * thread safe as against another shared pointer accessing the same
 * object in a different thread.  It is thus suitable for use in
 * different standard 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 pointer is to be modified in one
 *     thread (by assigning to the pointer so that it references a
 *     different object, or by moving from it), and copied (assigned
 *     from or used as the argument of a copy constructor), accessed,
 *     destroyed or modified in another thread, a mutex for that
 *     instance of shared pointer is required.
 * 3.  Objects referenced by shared pointers 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
 *     pointers referencing it (and indeed, when not using shared
 *     pointers at all).
 *
 * As mentioned, by default glib atomic functions are used to provide
 * thread-safe manipulation of the reference count.  However, the
 * symbol CGU_SHARED_LOCK_PTR_USE_MUTEX can be defined so that the
 * library uses mutexes instead, which might be useful for some
 * debugging purposes.  Note that if CGU_SHARED_LOCK_PTR_USE_MUTEX is
 * to be defined, this is best done by textually amending the
 * shared_ptr.h header file before the library is compiled.  This will
 * ensure that everything in the program and the library which
 * includes the shared_ptr.h header is guaranteed to see the same
 * definitions so that the C++ standard's one-definition-rule is
 * complied with.
 *
 * @b Comparison @b with @b std::shared_ptr
 *
 * Most of the things that can be done by this class can be done by
 * using std::shared_ptr from C++11/14, but this class is retained in
 * the c++-gtk-utils library not only to retain compatibility with
 * series 1.2 of the library, but also to cater for some cases not met
 * (or not so easily met) by std::shared_ptr:
 *
 * 1. Glib memory slices provide an efficient small object allocator
 *    (they are likely to be significantly more efficient than global
 *    operator new()/new[](), which generally hand off to malloc(),
 *    and whilst malloc() is good for large block allocations it is
 *    generally poor as a small object allocator).  Internal
 *    Cgu::SharedLockPtr allocation using glib memory slices can be
 *    achieved by compiling the library with the
 *    \--with-glib-memory-slices-no-compat configuration option.
 * 2. If glib memory slices are not used (which do not throw),
 *    constructing a shared pointer for a new managed object (or
 *    calling reset() for a new managed object) might throw if
 *    internal allocation fails.  Although by default the
 *    Cgu::SharedLockPtr implementation will delete the new managed
 *    object in such a case, it also provides an alternative
 *    constructor and reset() method which instead enable the new
 *    object to be accessed via the thrown exception object so that
 *    user code can decide what to do; std::shared_ptr deletes the new
 *    object in every case.
 * 3. A user can explicitly state whether the shared pointer object is
 *    to have atomic increment and decrement-and-test with respect to
 *    the reference count so that the reference count is thread safe
 *    ('no' in the case of Cgu::SharedPtr, and 'yes' in the case of
 *    Cgu::SharedLockPtr).  Using atomic functions is unnecessary if
 *    the managed object concerned is only addressed in one thread
 *    (and might cause unwanted cache flushing in certain
 *    circumstances).  std::shared_ptr will generally always use
 *    atomic functions with respect to its reference count in a
 *    multi-threaded program.
 *
 * In favour of std::shared_ptr, it has an associated
 * std::make_shared() factory function which will construct both the
 * referenced object and the shared pointer's reference count within a
 * single memory block when the first shared pointer managing a
 * particular object is constructed.  Cgu::SharedPtr and
 * Cgu::SharedLockPtr always allocate these separately, but this is
 * partly mitigated by the use of glib memory slices to allocate the
 * reference count where the \--with-glib-memory-slices-no-compat
 * configuration option is chosen.
 *
 * In addition, std::shared_ptr has an associated std::weak_ptr class,
 * which Cgu::SharedLockPtr does not (there is a Cgu::GobjWeakHandle
 * class, but that is cognate with Cgu::GobjHandle and is only usable
 * with GObjects), and shared_ptr objects also have some atomic store,
 * load and exchange functions provided for them which enable
 * concurrent modifications of the same instance of shared_ptr in
 * different threads to have defined results.
 *
 * If the library is compiled with the
 * \--with-glib-memory-slices-no-compat configuration option, as
 * mentioned Cgu::SharedLockPtr constructs its reference counting
 * internals using glib memory slices.  Although it is safe in a
 * multi-threaded program if glib < 2.32 is installed to construct a
 * static SharedLockPtr object in global namespace (that is, prior to
 * g_thread_init() being called) by means of the default constructor
 * and/or a pointer argument of NULL, it is not safe if constructed
 * with a non-NULL pointer value.  If glib >= 2.32 is installed,
 * global objects with memory slices are safe in all
 * circumstances. (Having said that, it would be highly unusual to
 * have global SharedLockPtr objects.)
 */

template <class T> class SharedLockPtr {

#ifndef DOXYGEN_PARSING
  struct RefItems {
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    Thread::Mutex* mutex_p;
    unsigned int* ref_count_p;
#else
    gint* ref_count_p;
#endif
    T* obj_p;
  } ref_items;
#endif

  // SharedLockPtr<T>::unreference() does not throw if the destructor of the
  // contained object does not throw, because  Thread::Mutex::~Mutex(),
  // Thread::Mutex::lock() and Thread::Mutex::unlock() do not throw
  void unreference() {
    // we can (and should) check whether ref_items.ref_count_p is NULL without
    // a lock, because that member is specific to this SharedLockPtr object.
    // Only the integer pointed to by it is shared amongst SharedLockPtr
    // objects and requires locking
    if (!ref_items.ref_count_p) return;
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    ref_items.mutex_p->lock();
    --(*ref_items.ref_count_p);
    if (*ref_items.ref_count_p == 0) {
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      g_slice_free(unsigned int, ref_items.ref_count_p);
# else
      delete ref_items.ref_count_p;
# endif
      ref_items.mutex_p->unlock();
      delete ref_items.mutex_p;
      delete ref_items.obj_p;
    }
    else ref_items.mutex_p->unlock();
#else
    if (g_atomic_int_dec_and_test(ref_items.ref_count_p)) {
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      g_slice_free(gint, ref_items.ref_count_p);
# else
      delete ref_items.ref_count_p;
# endif
      delete ref_items.obj_p;
    }
#endif
  }

  // SharedLockPtr<T>::reference() does not throw because
  // Thread::Mutex::Lock::Lock() and Thread::Mutex::Lock::~Lock() do not throw
  void reference() {
    // we can (and should) check whether ref_items.ref_count_p is NULL without
    // a lock, because that member is specific to this SharedLockPtr object.
    // Only the integer pointed to by it is shared amongst SharedLockPtr
    // objects and requires locking
    if (!ref_items.ref_count_p) return;
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    Thread::Mutex::Lock lock(*ref_items.mutex_p);
    ++(*ref_items.ref_count_p);
#else
    g_atomic_int_inc(ref_items.ref_count_p);
#endif
  }

public:
/**
 * Constructor taking an unmanaged object.
 * @param ptr The object which the SharedLockPtr is to manage (if
 * any).
 * @exception std::bad_alloc This constructor will not throw if the
 * 'ptr' argument has a NULL value (the default), otherwise it might
 * throw std::bad_alloc if memory is exhausted and the system throws
 * in that case.  If such an exception is thrown, this constructor is
 * exception safe (it does not leak resources), but as well as
 * cleaning itself up this constructor will also delete the managed
 * object passed to it to avoid a memory leak.  If such automatic
 * deletion is not wanted in that case, use the version of this
 * constructor taking a Cgu::SharedPtrAllocFail::Leave tag argument.
 * @note 1. std::bad_alloc will not be thrown if the library has been
 * installed using the \--with-glib-memory-slices-no-compat
 * configuration option: instead glib will terminate the program if it
 * is unable to obtain memory from the operating system.
 * @note 2. By default, glib atomic functions are used to provide
 * thread-safe manipulation of the reference count.  However, the
 * header file shared_ptr.h can be textually amended before the
 * library is compiled to define the symbol
 * CGU_SHARED_LOCK_PTR_USE_MUTEX so as to use mutexes instead, which
 * might be useful for some debugging purposes.  Were that to be done,
 * Cgu::Thread::MutexError might be thrown by this constructor if
 * initialization of the mutex fails.
 */
  explicit SharedLockPtr(T* ptr = 0) {

    if ((ref_items.obj_p = ptr)) { // not NULL
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
      try {
	ref_items.mutex_p = new Thread::Mutex;
      }
      catch (...) {
	delete ptr;  // if allocating the object referenced by ref_items.mutex_p
                     // has failed then delete the object to be referenced to
                     // avoid a memory leak
	throw;
      }
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(unsigned int);
      *ref_items.ref_count_p = 1;
# else
      try {
	ref_items.ref_count_p = new unsigned int(1);
      }
      catch (...) {
	delete ref_items.mutex_p;
	delete ptr;
	throw;
      }
# endif
#else
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(gint);
      *ref_items.ref_count_p = 1;
# else
      try {
	ref_items.ref_count_p = new gint(1);
      }
      catch (...) {
	delete ptr;  // if allocating the int referenced by ref_items.ref_count_p
                     // has failed then delete the object to be referenced to
                     // avoid a memory leak
	throw;
      }
# endif
#endif
    }
    else {
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
      ref_items.mutex_p = 0;  // make sure the value is valid as we may assign it
#endif
      ref_items.ref_count_p = 0;
    }
  }

/**
 * Constructor taking an unmanaged object.
 * @param ptr The object which the SharedLockPtr is to manage.
 * @param tag Passing the tag emumerator
 * Cgu::SharedPtrAllocFail::leave causes this constructor not to
 * delete the new managed object passed as the 'ptr' argument in the
 * event of internal allocation in this method failing because of
 * memory exhaustion (in that event, Cgu::SharedPtrError will be
 * thrown).
 * @exception Cgu::SharedPtrError This constructor might throw
 * Cgu::SharedPtrError if memory is exhausted and the system would
 * otherwise throw std::bad_alloc in that case.  This constructor is
 * exception safe (it does not leak resources), and if such an
 * exception is thrown it will clean itself up, but it will not
 * attempt to delete the new managed object passed to it.  Access to
 * the object passed to the 'ptr' argument can be obtained via the
 * thrown Cgu::SharedPtrError object.
 * @note 1. On systems with over-commit/lazy-commit combined with
 * virtual memory (swap), it is rarely useful to check for memory
 * exhaustion, so in those cases this version of the constructor will
 * not be useful.
 * @note 2. If the library has been installed using the
 * \--with-glib-memory-slices-no-compat configuration option this
 * version of the constructor will also not be useful: instead glib
 * will terminate the program if it is unable to obtain memory from
 * the operating system.
 * @note 3. By default, glib atomic functions are used to provide
 * thread-safe manipulation of the reference count.  However, the
 * header file shared_ptr.h can be textually amended before the
 * library is compiled to define the symbol
 * CGU_SHARED_LOCK_PTR_USE_MUTEX so as to use mutexes instead, which
 * might be useful for some debugging purposes.  Were that to be done,
 * Cgu::SharedPtrError might be thrown by this constructor if
 * initialization of the mutex fails (even if the
 * \--with-glib-memory-slices-no-compat configuration option is
 * chosen).
 */
  SharedLockPtr(T* ptr, Cgu::SharedPtrAllocFail::Leave tag) {

    if ((ref_items.obj_p = ptr)) { // not NULL
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
      try {
	ref_items.mutex_p = new Thread::Mutex;
      }
      catch (std::bad_alloc&) { // as we are not rethrowing, make NPTL friendly
	throw SharedPtrError<T>(ptr);
      }
      catch (Thread::MutexError&) { // as we are not rethrowing, make NPTL friendly
	throw SharedPtrError<T>(ptr);
      }
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(unsigned int);
      *ref_items.ref_count_p = 1;
# else
      try {
	ref_items.ref_count_p = new unsigned int(1);
      }
      catch (std::bad_alloc&) {
	delete ref_items.mutex_p;
	throw SharedPtrError<T>(ptr);
      }
# endif
#else
# ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
      ref_items.ref_count_p = g_slice_new(gint);
      *ref_items.ref_count_p = 1;
# else
      try {
	ref_items.ref_count_p = new gint(1);
      }
      catch (std::bad_alloc&) { // as we are not rethrowing, make NPTL friendly
	throw SharedPtrError<T>(ptr);
      }
# endif
#endif
    }
    else {
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
      ref_items.mutex_p = 0;  // make sure the value is valid as we may assign it
#endif
      ref_items.ref_count_p = 0;
    }
  }

/**
 * Causes the SharedLockPtr to cease to manage its managed object (if
 * any), deleting it if this is the last SharedLockPtr object managing
 * it.  If the argument passed is not NULL, the SharedLockPtr object
 * will manage the new object passed (which must not be managed by any
 * other SharedLockPtr object).  This method is exception safe, but
 * see the comments below on std::bad_alloc.
 * @param ptr NULL (the default), or a new unmanaged object to manage.
 * @exception std::bad_alloc This method will not throw if the 'ptr'
 * argument has a NULL value (the default) and the destructor of a
 * managed object does not throw, otherwise it might throw
 * std::bad_alloc if memory is exhausted and the system throws in that
 * case.  Note that if such an exception is thrown then this method
 * will do nothing (it is strongly exception safe and will continue to
 * manage the object it was managing prior to the call), except that
 * it will delete the new managed object passed to it to avoid a
 * memory leak.  If such automatic deletion in the event of such an
 * exception is not wanted, use the reset() method taking a
 * Cgu::SharedPtrAllocFail::Leave tag type as its second argument.
 * @note 1. std::bad_alloc will not be thrown if the library has been
 * installed using the \--with-glib-memory-slices-no-compat
 * configuration option: instead glib will terminate the program if it
 * is unable to obtain memory from the operating system.
 * @note 2. By default, glib atomic functions are used to provide
 * thread-safe manipulation of the reference count.  However, the
 * header file shared_ptr.h can be textually amended before the
 * library is compiled to define the symbol
 * CGU_SHARED_LOCK_PTR_USE_MUTEX so as to use mutexes instead, which
 * might be useful for some debugging purposes.  Were that to be done,
 * Cgu::Thread::MutexError might be thrown by this method if
 * initialization of the mutex fails.
 * @note 3. A SharedLockPtr object protects its reference count but
 * not the managed object or its other internals.  The reset() method
 * should not be called by one thread in respect of a particular
 * SharedLockPtr object while another thread may be operating on,
 * copying or dereferencing the same instance of SharedLockPtr.  It is
 * thread-safe as against another instance of SharedLockPtr managing
 * the same object.
 */
  void reset(T* ptr = 0) {
    SharedLockPtr tmp(ptr);
    std::swap(ref_items, tmp.ref_items);
  }

/**
 * Causes the SharedLockPtr to cease to manage its managed object (if
 * any), deleting it if this is the last SharedLockPtr object managing
 * it.  The SharedLockPtr object will manage the new object passed
 * (which must not be managed by any other SharedLockPtr object).
 * This method is exception safe, but see the comments below on
 * Cgu::SharedPtrError.
 * @param ptr A new unmanaged object to manage (if no new object is to
 * be managed, use the version of reset() taking a default value of
 * NULL).
 * @param tag Passing the tag emumerator
 * Cgu::SharedPtrAllocFail::leave causes this method not to delete the
 * new managed object passed as the 'ptr' argument in the event of
 * internal allocation in this method failing because of memory
 * exhaustion (in that event, Cgu::SharedPtrError will be thrown).
 * @exception Cgu::SharedPtrError This method might throw
 * Cgu::SharedPtrError if memory is exhausted and the system would
 * otherwise throw std::bad_alloc in that case.  Note that if such an
 * exception is thrown then this method will do nothing (it is
 * strongly exception safe and will continue to manage the object it
 * was managing prior to the call), and it will not attempt to delete
 * the new managed object passed to it.  Access to the object passed
 * to the 'ptr' argument can be obtained via the thrown
 * Cgu::SharedPtrError object.
 * @note 1. A SharedLockPtr object protects its reference count but
 * not the managed object or its other internals.  The reset() method
 * should not be called by one thread in respect of a particular
 * SharedLockPtr object while another thread may be operating on,
 * copying or dereferencing the same instance of SharedLockPtr.  It is
 * thread-safe as against another instance of SharedLockPtr managing
 * the same object.
 * @note 2. On systems with over-commit/lazy-commit combined with
 * virtual memory (swap), it is rarely useful to check for memory
 * exhaustion, so in those cases this version of the reset() method
 * will not be useful.
 * @note 3. If the library has been installed using the
 * \--with-glib-memory-slices-no-compat configuration option this
 * version of the reset() method will also not be useful: instead glib
 * will terminate the program if it is unable to obtain memory from
 * the operating system.
 * @note 4. By default, glib atomic functions are used to provide
 * thread-safe manipulation of the reference count.  However, the
 * header file shared_ptr.h can be textually amended before the
 * library is compiled to define the symbol
 * CGU_SHARED_LOCK_PTR_USE_MUTEX so as to use mutexes instead, which
 * might be useful for some debugging purposes.  Were that to be done,
 * Cgu::SharedPtrError might be thrown by this method if
 * initialization of the mutex fails (even if the
 * \--with-glib-memory-slices-no-compat configuration option is
 * chosen).
 */
  void reset(T* ptr, Cgu::SharedPtrAllocFail::Leave tag) {
    SharedLockPtr tmp(ptr, tag);
    std::swap(ref_items, tmp.ref_items);
  }

 /**
  * This copy constructor does not throw.
  * @param sh_ptr The shared pointer to be copied.
  */
  SharedLockPtr(const SharedLockPtr& sh_ptr) {
    ref_items = sh_ptr.ref_items;
    reference();
  }

 /**
  * The move constructor does not throw.  It has move semantics.
  * @param sh_ptr The shared pointer to be moved.
  */
  SharedLockPtr(SharedLockPtr&& sh_ptr) {
    ref_items = sh_ptr.ref_items;
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    sh_ptr.ref_items.mutex_p = 0;  // make sure the value is valid as we may assign it
#endif
    sh_ptr.ref_items.ref_count_p = 0;
    sh_ptr.ref_items.obj_p = 0;
  }

  template <class U> friend class SharedLockPtr;

 /**
  * A version of the copy constructor which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * copy constructor does not throw.
  * @param sh_ptr The shared pointer to be copied.
  */
  template <class U> SharedLockPtr(const SharedLockPtr<U>& sh_ptr) {
    // because we are allowing an implicit cast from derived to
    // base class referenced object, we need to assign from each
    // member of sh_ptr.ref_items separately
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    ref_items.mutex_p = sh_ptr.ref_items.mutex_p;
#endif
    ref_items.ref_count_p = sh_ptr.ref_items.ref_count_p;
    ref_items.obj_p = sh_ptr.ref_items.obj_p;
    reference();
  }

 /**
  * A version of the move constructor which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * move constructor does not throw.
  * @param sh_ptr The shared pointer to be moved.
  */
  template <class U> SharedLockPtr(SharedLockPtr<U>&& sh_ptr) {
    // because we are allowing an implicit cast from derived to
    // base class referenced object, we need to assign from each
    // member of sh_ptr.ref_items separately
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    ref_items.mutex_p = sh_ptr.ref_items.mutex_p;
#endif
    ref_items.ref_count_p = sh_ptr.ref_items.ref_count_p;
    ref_items.obj_p = sh_ptr.ref_items.obj_p;

#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    sh_ptr.ref_items.mutex_p = 0;  // make sure the value is valid as we may assign it
#endif
    sh_ptr.ref_items.ref_count_p = 0;
    sh_ptr.ref_items.obj_p = 0;
  }

 /**
  * This method (and so copy or move assignment) does not throw unless
  * the destructor of a managed object throws.
  * @param sh_ptr the assignor.
  * @return The SharedLockPtr object after assignment.
  */
  // having a value type as the argument, rather than reference to const
  // and then initialising a tmp object, gives the compiler more scope
  // for optimisation, and also caters for r-values without a separate
  // overload
  SharedLockPtr& operator=(SharedLockPtr sh_ptr) {
    std::swap(ref_items, sh_ptr.ref_items);
    return *this;
  }

 /**
  * A version of the assignment operator which enables pointer type
  * conversion (assuming the type passed is implicitly type
  * convertible to the managed type, such as a derived type).  This
  * method does not throw unless the destructor of a managed object
  * throws.
  * @param sh_ptr the assignor.
  * @return The SharedLockPtr object after assignment.
  */
  template <class U> SharedLockPtr& operator=(const SharedLockPtr<U>& sh_ptr) {
    return operator=(SharedLockPtr(sh_ptr));
  }
  
 /**
  * A version of the operator for move assignment which enables
  * pointer type conversion (assuming the type passed is implicitly
  * type convertible to the managed type, such as a derived type).
  * This method does not throw unless the destructor of a managed
  * object throws.
  * @param sh_ptr the shared pointer to be moved.
  * @return The SharedLockPtr object after the move operation.
  */
  template <class U> SharedLockPtr& operator=(SharedLockPtr<U>&& sh_ptr) {
    return operator=(SharedLockPtr(std::move(sh_ptr)));
  }

 /**
  * This method does not throw.
  * @return A pointer to the managed object (or NULL if none is
  * managed).
  */
  T* get() const {return ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return A reference to the managed object.
  */
  T& operator*() const {return *ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return A pointer to the managed object (or NULL if none is
  * managed).
  */
  T* operator->() const {return ref_items.obj_p;}

 /**
  * This method does not throw.
  * @return The number of SharedLockPtr objects referencing the
  * managed object (or 0 if none is managed by this SharedLockPtr).
  * @note The return value may not be valid if another thread has
  * changed the reference count before the value returned by this
  * method is acted on.  It is provided as a utility, but may not be
  * meaningful, depending on the intended usage.
  */
  unsigned int get_refcount() const {
    if (!ref_items.ref_count_p) return 0;
#ifdef CGU_SHARED_LOCK_PTR_USE_MUTEX
    Thread::Mutex::Lock lock(*ref_items.mutex_p);
    return *ref_items.ref_count_p;
#else
    return g_atomic_int_get(ref_items.ref_count_p);
#endif
  }

 /**
  * The destructor does not throw unless the destructor of a managed
  * object throws - that should never happen.
  */
  ~SharedLockPtr() {unreference();}
};

#if defined(CGU_USE_SMART_PTR_COMPARISON) || defined(DOXYGEN_PARSING)

// we can use built-in operator == when comparing pointers referencing
// different objects of the same type
/**
 * @ingroup handles
 *
 * This comparison operator does not throw.  It compares the addresses
 * of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator==(const SharedPtr<T>& s1, const SharedPtr<T>& s2) {
  return (s1.get() == s2.get());
}

/**
 * @ingroup handles
 *
 * This comparison operator does not throw.  It compares the addresses
 * of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator!=(const SharedPtr<T>& s1, const SharedPtr<T>& s2) {
  return !(s1 == s2);
}

// we must use std::less rather than the < built-in operator for
// pointers to objects not within the same array or object: "For
// templates greater, less, greater_equal, and less_equal, the
// specializations for any pointer type yield a total order, even if
// the built-in operators <, >, <=, >= do not." (para 20.3.3/8).
/**
 * @ingroup handles
 *
 * This comparison operator does not throw unless std::less applied to
 * pointer types throws (which it would not do with any sane
 * implementation).  It compares the addresses of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator<(const SharedPtr<T>& s1, const SharedPtr<T>& s2) {
  return std::less<T*>()(s1.get(), s2.get());
}

/**
 * @ingroup handles
 *
 * This comparison operator does not throw.  It compares the addresses
 * of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator==(const SharedLockPtr<T>& s1, const SharedLockPtr<T>& s2) {
  return (s1.get() == s2.get());
}

/**
 * @ingroup handles
 *
 * This comparison operator does not throw.  It compares the addresses
 * of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator!=(const SharedLockPtr<T>& s1, const SharedLockPtr<T>& s2) {
  return !(s1 == s2);
}

/**
 * @ingroup handles
 *
 * This comparison operator does not throw unless std::less applied to
 * pointer types throws (which it would not do with any sane
 * implementation).  It compares the addresses of the managed objects.
 *
 * Since 2.0.0-rc2
 */
template <class T>
bool operator<(const SharedLockPtr<T>& s1, const SharedLockPtr<T>& s2) {
  return std::less<T*>()(s1.get(), s2.get());
}

#endif // CGU_USE_SMART_PTR_COMPARISON

} // namespace Cgu

// doxygen produces long filenames that tar can't handle:
// we have generic documentation for std::hash specialisations
// in doxygen.main.in
#if defined(CGU_USE_SMART_PTR_COMPARISON) && !defined(DOXYGEN_PARSING)
/* These structs allow SharedPtr and SharedLockPtr objects to be keys
   in unordered associative containers */
namespace std {
template <class T>
struct hash<Cgu::SharedPtr<T>> {
  typedef std::size_t result_type;
  typedef Cgu::SharedPtr<T> argument_type;
  result_type operator()(const argument_type& s) const {
    // this is fine: std::hash structs do not normally contain data and
    // std::hash<T*> certainly won't, so we don't have overhead constructing
    // std::hash<T*> on the fly
    return std::hash<T*>()(s.get());
  }
};
template <class T>
struct hash<Cgu::SharedLockPtr<T>> {
  typedef std::size_t result_type;
  typedef Cgu::SharedLockPtr<T> argument_type;
  result_type operator()(const argument_type& s) const {
    // this is fine: std::hash structs do not normally contain data and
    // std::hash<T*> certainly won't, so we don't have overhead constructing
    // std::hash<T*> on the fly
    return std::hash<T*>()(s.get());
  }
};
} // namespace std
#endif // CGU_USE_SMART_PTR_COMPARISON

#endif
