shared_handle.h
Go to the documentation of this file.
/* Copyright (C) 2004 to 2013 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_HANDLE_H
#define CGU_SHARED_HANDLE_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
// SharedLockHandle class
/* #define CGU_SHARED_LOCK_HANDLE_USE_MUTEX 1 */
#include <exception>
#include <new>
#include <functional> // for std::less and std::hash<T*>
#include <utility> // for std::swap
#include <cstddef> // for std::size_t
#include <cstdlib>
#include <glib.h>
#ifdef CGU_SHARED_LOCK_HANDLE_USE_MUTEX
#include <c++-gtk-utils/mutex.h>
#endif
#include <c++-gtk-utils/cgu_config.h>
/**
* @addtogroup handles handles and smart pointers
*/
namespace Cgu {
/**
* @class SharedHandle shared_handle.h c++-gtk-utils/shared_handle.h
* @brief This is a generic class for managing the lifetime of objects
* allocated on freestore.
* @ingroup handles
* @sa SharedLockHandle
* @sa ScopedHandle
* @sa SharedHandleError
* @sa GcharSharedHandle
* @sa GerrorSharedHandle
* @sa StandardArrayDelete CFree GFree GerrorFree GSliceFree GSliceFreeSize GSliceDestroy
*
* The SharedHandle class is similar to the SharedPtr 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 SharedHandle 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(), g_list_free()
* or g_slice_free(). Instances (such as @ref GcharScopedHandleAnchor
* "GcharScopedHandle", @ref GcharSharedHandleAnchor
* "GcharSharedHandle", @ref GerrorSharedHandleAnchor
* "GerrorSharedHandle" and @ref GerrorScopedHandleAnchor
* "GerrorScopedHandle") typdef'ed for particular deleters can
* conveniently manage objects of any kind.
*
* To reflect the fact that it is just a handle for a pointer, it has
* different instantiation semantics from a SharedPtr object. A
* SharedPtr object is instantiated using this syntax:
*
* @code SharedPtr<ObjType> sh_ptr(new ObjType); @endcode
*
* A SharedHandle is instantiated using this syntax (note that the
* instantiated handle is for type T* and not T):
*
* @code SharedHandle<ObjType*> sh_handle(new ObjType[n]); @endcode
*
*
* Apart from the operatorT() type conversion operator (which returns
* the underlying pointer), the only other method to obtain the
* underlying pointer is the get() method. If the object referenced
* is an array allocated on the heap, to use indexing you could either
* do this:
*
* @code
* using namespace Cgu;
* SharedHandle<char*> handle(new char[10]);
* handle.get()[0] = 'a';
* std::cout << handle.get()[0] << std::endl;
* @endcode
*
* or this:
*
* @code
* using namespace Cgu;
* SharedHandle<char*> handle(new char[10]);
* handle[0] = 'a';
* std::cout << handle[0] << std::endl;
* @endcode
*
* There is also a SharedLockHandle class, which has a thread-safe
* reference count, and a ScopedHandle class, which deletes its object
* as soon as it goes out of scope. A ScopedHandle class can be
* viewed as a SharedHandle 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::auto_ptr<>.
*
* SharedHandle objects can be instantiated for pointers to constant
* objects (such as SharedHandle<const char*>), provided the deleter
* functor will take such pointers.
*
* This library provides StandardArrayDelete, CFree, GFree,
* GerrorFree, GSliceFree, GSliceFreeSize and GSliceDestroy deleter
* functors, which can be used as the second template parameter of the
* SharedHandle class. As mentioned above, StandardArrayDelete is the
* default, and some typedef'ed instances of SharedHandle for gchar
* (with the GFree deleter) and for GError (with the GerrorFree
* deleter) are provided.
*
* @b Comparison @b with @b std::shared_ptr
*
* Although the semantics of std::shared_ptr in C++11 are not
* particularly suited to managing either arrays or C objects with
* accessor functions (such as in glib), most of the things that can
* be done by this class can be done by using std::shared_ptr with a
* specialised deleter. However, 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. The Cgu::SharedHandle class takes its deleter as a template
* parameter, which means that typedefs can be used to enable
* handles for particular deleters to be easily created (and as
* mentioned, this library provides a number of pre-formed deleter
* functors and typedefs for them). With std::shared_ptr, custom
* deleters must be passed to the shared_ptr constructor on every
* occasion a shared_ptr is constructed to manage a new object (and
* they cannot be templated as a typedef).
* 2. 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::SharedHandle allocation using glib memory slices can be
* achieved by compiling the library with the
* \--with-glib-memory-slices-no-compat configuration option.
* 3. 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::SharedHandle 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.
* 4. A user can explicitly state whether the shared handle 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::SharedHandle, and 'yes' in the case of
* Cgu::SharedLockHandle). 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::weak_ptr
* class, which Cgu::SharedHandle 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::SharedHandle 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 SharedHandle 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 SharedHandle objects.)
*/
/********************* here are some deleter classes *******************/
/**
* @class StandardArrayDelete shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls the C++ delete[] expression.
* @ingroup handles
* @details This functor enables those classes to manage arrays
* created with the new expression. It is the default type of the
* second template paramenter of those classes.
*/
template <class T> class StandardArrayDelete {
public:
void operator()(T obj) {
delete[] obj;
}
};
/**
* @class CFree shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls std::free.
* @ingroup handles
* @details This functor enables those classes to manage memory
* allocated with std::malloc(), std::calloc() and std::realloc().
*/
class CFree {
public:
void operator()(const void* obj) noexcept {
std::free(const_cast<void*>(obj));
}
};
/**
* @class GFree shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls glib's g_free().
* @ingroup handles
* @details This functor enables those classes to manage memory
* allocated by glib or gtk+ functions which requires to be freed with
* g_free(). It is used in the typedefs @ref GcharSharedHandleAnchor
* "GcharSharedHandle" and @ref GcharScopedHandleAnchor
* "GcharScopedHandle".
*/
class GFree {
public:
void operator()(const void* obj) noexcept {
g_free(const_cast<void*>(obj));
}
};
/**
* @class GSliceFree shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls glib's g_slice_free1().
* @ingroup handles
*
* @details This functor enables those classes to manage a memory
* block allocated using glib memory slices. The managed memory block
* to be deleted by the GSliceFree functor must have the same size as
* the size of the object for which the functor is instantiated by
* pointer, as for example as allocated with the g_slice_new,
* g_slice_new0 or g_slice_dup macros (in other words, the GSliceFree
* template parameter must match the argument passed to those macros):
* see the example below. Use GSliceFreeSize where it is necessary or
* more convenient to have the size of the block to be freed as the
* template parameter. Use GSliceDestroy where the memory holds a C++
* object constructed in the memory by the global placement new
* expression.
*
* The type of the template argument for the functor is a pointer to
* the managed type: it is the same as the first template argument of
* the relevant SharedHandle, SharedLockHandle or ScopedHandle object.
* For example:
*
* @code
* using namespace Cgu;
* SharedHandle<MyStruct*, GSliceFree<MyStruct*> > h(g_slice_new(MyStruct));
* ...
* @endcode
*
* The availability of this functor is not dependent on the library
* having been installed with the \--with-glib-memory-slices-compat or
* \--with-glib-memory-slices-no-compat configuration option (see @ref
* Memory for further details of those options).
*/
template <class T> class GSliceFree {
public:
void operator()(T obj) noexcept {
g_slice_free1(sizeof(*obj), (void*)obj);
}
};
/**
* @class GSliceDestroy shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls glib's g_slice_free1(), but before
* doing so also explicitly calls the destructor of a C++ object
* constructed in the memory.
* @ingroup handles
*
* @details The managed memory block to be deleted by the
* GSliceDestroy functor must have the same size as the size of the
* object for which the functor is instantiated by pointer, as for
* example as allocated with the g_slice_new or g_slice_new0 macros
* (in other words, the GSliceDestroy template parameter must match
* the argument passed to those macros), and the memory block must
* have had that object constructed in it with the global placement
* new expression: see the example below. Sometimes it is more
* convenient to implement C++ objects in glib memory slices that way,
* rather than to have custom new and delete member operators of the
* classes concerned which use glib's g_slice_*(). However, a
* SharedHandle class with a GSliceDestroy deleter is not as easy to
* use as the SharedPtr class, as SharedHandle has no operator*() nor
* operator->() method (the get() method would have to be used to
* obtain the underlying pointer).
*
* One consequence of the static sizing (and so typing) of memory
* slices is that a GSliceDestroy object instantiated for the
* management of a particular class must not be used by a
* SharedHandle, SharedLockHandle or ScopedHandle object which
* attempts to manage a class derived from it. This comes back to the
* point that the GSliceDestroy template parameter must match the
* argument passed to the g_slice_new or g_slice_new0 macros.
*
* The type of the template argument for the functor is a pointer to
* the managed type: it is the same as the first template argument of
* the relevant SharedHandle, SharedLockHandle or ScopedHandle object.
* For example, to construct a SharedHandle managing an object of type
* MyClass to be constructed in a glib memory slice in an exception
* safe way:
*
* @code
* using namespace Cgu;
* SharedHandle<MyClass*, GSliceDestroy<MyClass*> > h; // won't throw
* { // scope block for p variable
* MyClass* p = g_slice_new(MyClass);
* try {new(p) MyClass;} // MyClass constructor might throw
* catch(...) {
* g_slice_free(MyClass, p);
* throw;
* }
* h.reset(p); // might throw but if so cleans up
* }
* ...
* @endcode
*
* The availability of this functor is not dependent on the library
* having been installed with the \--with-glib-memory-slices-compat or
* \--with-glib-memory-slices-no-compat configuration option (see @ref
* Memory for further details of those options).
*/
template <class T> class GSliceDestroy {
template <class U> void destroy(U& obj) {obj.~U();}
public:
void operator()(T obj) {
destroy(*obj);
g_slice_free1(sizeof(*obj), (void*)obj);
}
};
/**
* @class GSliceFreeSize shared_handle.h c++-gtk-utils/shared_handle.h
* @brief A deleter functor for use as the second (Dealloc) template
* parameter of the SharedHandle, SharedLockHandle or ScopedHandle
* template classes, which calls glib's g_slice_free1().
* @ingroup handles
*
* @details This functor enables those classes to manage memory
* allocated with g_slice_alloc(), g_slice_alloc0() or g_slice_copy().
* It is an alternative to using GSliceFree where, instead of the
* template parameter being a pointer to a particular managed type,
* the size of the memory block to be freed is passed, so enabling it
* to be more conveniently used to free memory containing arrays of
* built-in types or of PODSs. Use GSliceDestroy where the memory
* holds a C++ object constructed in the memory by the global
* placement new expression.
*
* The type of the template argument for the functor is an integer
* type (gsize) and is the size of the block to be managed. For
* example:
*
* @code
* using namespace Cgu;
* SharedHandle<char*, GSliceFreeSize<10> > h(static_cast<char*>(g_slice_alloc(10)));
* ...
* @endcode
*
* The availability of this functor is not dependent on the library
* having been installed with the \--with-glib-memory-slices-compat or
* \--with-glib-memory-slices-no-compat configuration option (see @ref
* Memory for further details of those options).
*/
template <gsize block_size> class GSliceFreeSize {
public:
void operator()(const void* obj) noexcept {
g_slice_free1(block_size, const_cast<void*>(obj));
}
};
/*
* we could provide a functor class for
* g_slice_free_chain_with_offset() such as:
*
* template <class T, gsize offset> class GSliceFreeChain {
* public:
* void operator()(T obj) {
* g_slice_free_chain_with_offset(sizeof(*obj), (void*)obj, offset);
* }
* };
*
* However, this is not going to be particularly useful because the
* G_STRUCT_OFFSET macro and/or C's offsetof macro, needed to provide
* the value for the offset parameter, do not work for other than
* PODSs. g_slice_free_chain_with_offset() is intended for internal
* implementations and in the event of a user wanting such memory
* management it is best achieved by having custom new[] and delete[]
* member operators of the class concerned which use glib's
* g_slice_*() directly.
*/
/********************* define some typedefs for Glib ******************/
template <class T, class Dealloc> class SharedHandle;
template <class T, class Dealloc> class ScopedHandle;
/**
* @typedef GcharSharedHandle.
* @brief A handle comprising a typed instance of the SharedHandle
* class for gchar* arrays and strings
* @anchor GcharSharedHandleAnchor
* @ingroup handles
* \#include <c++-gtk-utils/shared_handle.h>
*/
typedef SharedHandle<gchar*, GFree> GcharSharedHandle;
/**
* @typedef GcharScopedHandle.
* @brief A handle comprising a typed instance of the ScopedHandle
* class for gchar* arrays and strings
* @anchor GcharScopedHandleAnchor
* @ingroup handles
* \#include <c++-gtk-utils/shared_handle.h>
*/
typedef ScopedHandle<gchar*, GFree> GcharScopedHandle;
/******************* now the handle class definitions *****************/
/**
* @class SharedHandleError shared_handle.h c++-gtk-utils/shared_handle.h
* @brief This is an exception struct thrown as an alternative to
* deleting a managed object when internal memory allocation for
* SharedHandle or SharedLockHandle fails in their reset() method or
* in their constructor which takes a pointer.
* @sa SharedHandle SharedLockHandle SharedHandleAllocFail
* @ingroup handles
*
* This is an exception struct thrown as an alternative to deleting a
* managed object when SharedHandle<T>::SharedHandle(T),
* SharedLockHandle<T>::SharedLockHandle(T), SharedHandle<T>::reset(T)
* or SharedLockHandle<T>::reset(T) would otherwise throw
* std::bad_alloc. To make those methods do that,
* Cgu::SharedHandleAllocFail::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 or array 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;
* SharedHandle<T*> s; // doesn't throw
* try {
* s.reset(new T[2], SharedHandleAllocFail::leave); // both T allocation and reset() might throw
* }
* catch (std::bad_alloc&) {
* ...
* }
* catch (SharedHandleError<T*>& e) {
* e.obj[0].do_something();
* e.obj[1].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 SharedHandleError (if the call to the new expression in
* the reset() method fails after a valid T object has been
* constructed).
*/
template <class T> struct SharedHandleError: public std::exception {
T obj;
virtual const char* what() const throw() {return "SharedHandleError\n";}
SharedHandleError(T p): obj(p) {}
};
/**
* enum Cgu::SharedHandleAllocFail::Leave
* The enumerator Cgu::SharedHandleAllocFail::leave is passed as the
* second argument of the reset() method of SharedHandle or
* SharedLockHandle in order to prevent the method deleting the object
* passed to it if reset() fails internally because of memory
* exhaustion.
* @ingroup handles
*/
namespace SharedHandleAllocFail {
enum Leave {leave};
}
template <class T, class Dealloc = StandardArrayDelete<T>> class SharedHandle {
Dealloc deleter;
#ifndef DOXYGEN_PARSING
struct RefItems {
unsigned int* ref_count_p;
T obj;
} 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
deleter(ref_items.obj);
}
}
void reference() noexcept {
if (!ref_items.ref_count_p) return;
++(*ref_items.ref_count_p);
}
public:
/**
* Constructor taking an unmanaged object.
* @param ptr The object which the SharedHandle 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::SharedHandleAllocFail::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 SharedHandle(T ptr = 0) {
if ((ref_items.obj = 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 (...) {
deleter(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 SharedHandle is to manage
* @param tag Passing the tag emumerator
* Cgu::SharedHandleAllocFail::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::SharedHandleError will be
* thrown).
* @exception Cgu::SharedHandleError This constructor might throw
* Cgu::SharedHandleError 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::SharedHandleError 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.
*/
SharedHandle(T ptr, Cgu::SharedHandleAllocFail::Leave tag) {
if ((ref_items.obj = 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 SharedHandleError<T>(ptr);
}
#endif
}
else ref_items.ref_count_p = 0;
}
/**
* Causes the SharedHandle to cease to manage its managed object (if
* any), deleting it if this is the last SharedHandle object managing
* it. If the argument passed is not NULL, the SharedHandle object
* will manage the new object passed (which must not be managed by any
* other SharedHandle 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::SharedHandleAllocFail::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) {
SharedHandle tmp(ptr);
std::swap(ref_items, tmp.ref_items);
}
/**
* Causes the SharedHandle to cease to manage its managed object (if
* any), deleting it if this is the last SharedHandle object managing
* it. The SharedHandle object will manage the new object passed
* (which must not be managed by any other SharedHandle object). This
* method is exception safe, but see the comments below on
* Cgu::SharedHandleError.
* @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::SharedHandleAllocFail::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::SharedHandleError will be thrown).
* @exception Cgu::SharedHandleError This method might throw
* Cgu::SharedHandleError 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::SharedHandleError 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::SharedHandleAllocFail::Leave tag) {
SharedHandle tmp(ptr, tag);
std::swap(ref_items, tmp.ref_items);
}
/**
* The copy constructor does not throw.
* @param sh_hand The handle to be copied.
*/
SharedHandle(const SharedHandle& sh_hand) noexcept {
ref_items = sh_hand.ref_items;
reference();
}
/**
* The move constructor does not throw. It has move semantics.
* @param sh_hand The handle to be moved.
*/
SharedHandle(SharedHandle&& sh_hand) noexcept {
ref_items = sh_hand.ref_items;
sh_hand.ref_items.ref_count_p = 0;
sh_hand.ref_items.obj = 0;
}
/**
* This method (and so copy or move assignment) does not throw unless
* the destructor of a managed object throws.
* @param sh_hand the assignor.
* @return The SharedHandle 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
SharedHandle& operator=(SharedHandle sh_hand) {
std::swap(ref_items, sh_hand.ref_items);
return *this;
}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
T get() const noexcept {return ref_items.obj;}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
operator T() const noexcept {return ref_items.obj;}
/**
* This method does not throw.
* @return The number of SharedHandle objects referencing the managed
* object (or 0 if none is managed by this SharedHandle).
*/
unsigned int get_refcount() const noexcept {return (ref_items.ref_count_p) ? *ref_items.ref_count_p : 0;}
/**
* The destructor does not throw unless the destructor of a handled
* object throws - that should never happen.
*/
~SharedHandle() {unreference();}
};
/**
* @class ScopedHandle shared_handle.h c++-gtk-utils/shared_handle.h
* @brief This is a generic scoped class for managing the lifetime of objects
* allocated on freestore.
* @ingroup handles
* @sa SharedHandle SharedLockHandle SharedHandleError
* @sa StandardArrayDelete CFree GFree GerrorFree GSliceFree GSliceFreeSize GSliceDestroy
*
* This class deletes its object as soon as it goes out of scope. It
* can be viewed as a SharedHandle which cannot be copy assigned to or
* used as the argument to a copy constructor and therefore which
* cannot have a reference count of more than 1.
*
* ScopedHandle objects can be instantiated for pointers to constant
* objects (such as ScopedHandle<const char*>), provided the deleter
* functor will take such pointers.
*
* This library provides StandardArrayDelete, CFree, GFree,
* GerrorFree, GSliceFree, GSliceFreeSize and GSliceDestroy deleter
* functors, which can be used as the second template parameter of the
* ScopedHandle class. StandardArrayDelete is the default, and some
* typedef'ed instances of ScopedHandle for gchar (with the GFree
* deleter) and for GError (with the GerrorFree deleter) are provided:
* @ref GcharScopedHandleAnchor "GcharScopedHandle" and @ref
* GerrorScopedHandleAnchor "GerrorScopedHandle")
*
* @b Comparison @b with @b std::unique_ptr
*
* This class is mainly retained to retain compatibility with series
* 1.2 of the library, since most of the things that can be done with
* it can also be done using std::unique_ptr. However, this class is
* a little easier to use when managing objects with associated C
* functions (such as in glib), because it provides a type conversion
* operator.
*
* From version 2.2.2, this class has a move constructor and move
* assignment operator. Prior to that, it could not be moved from or
* to.
*/
template <class T, class Dealloc = StandardArrayDelete<T>> class ScopedHandle {
Dealloc deleter;
T obj;
public:
/**
* This class cannot be copied. The copy constructor is deleted.
*/
ScopedHandle(const ScopedHandle&) = delete;
/**
* This class cannot be copied. The copy assignment operator is
* deleted.
*/
ScopedHandle& operator=(const ScopedHandle&) = delete;
/**
* The move constructor does not throw.
* @param sc_hand The handle to be moved.
*
* Since 2.0.19/2.2.2
*/
ScopedHandle(ScopedHandle&& sc_hand) noexcept {
obj = sc_hand.obj;
sc_hand.obj = 0;
}
/**
* The move assignment operator. It will delete the object managed
* prior to the move, if any. It does not throw unless the destructor
* of that object throws.
* @param sc_hand The handle to be moved.
* @return The ScopedHandle object after move assignment.
*
* Since 2.0.19/2.2.2
*/
ScopedHandle& operator=(ScopedHandle&& sc_hand) {
reset(sc_hand.release());
return *this;
}
/**
* This constructor does not throw.
* @param ptr The object which the ScopedHandle is to manage (if
* any).
*
* ScopedHandle objects can be instantiated for pointers to constant
* objects (such as SharedHandle<const char*>), provided the deleter
* functor will take such pointers.
*/
explicit ScopedHandle(T ptr = 0) noexcept: obj(ptr) {}
/**
* Causes the ScopedHandle to delete its managed object (if any), and
* if the argument passed is not NULL, the ScopedHandle object will
* manage the new object passed (which must not be managed by any
* other ScopedHandle object). This method does not throw (assuming
* the destructor of a managed object does not throw).
* @param ptr NULL (the default), or a new unmanaged object to manage.
*/
void reset(T ptr = 0) {
std::swap(obj, ptr);
if (ptr) deleter(ptr); // ptr now points to the original managed object
}
/**
* Causes the ScopedHandle to cease to manage the handled object, but
* does not delete that object. This method does not throw.
* @return A pointer to the previously handled object (or NULL if none
* was handled).
*/
T release() noexcept {T tmp = obj; obj = 0; return tmp;}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
T get() const noexcept {return obj;}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
operator T() const noexcept {return obj;}
/**
* The destructor does not throw unless the destructor of a handled
* object throws - that should never happen.
*/
~ScopedHandle() {if (obj) deleter(obj);}
};
/**
* @class SharedLockHandle shared_handle.h c++-gtk-utils/shared_handle.h
* @brief This is a generic class for managing the lifetime of objects
* allocated on freestore, with a thread safe reference count..
* @ingroup handles
* @sa SharedHandle ScopedHandle SharedHandleError
* @sa StandardArrayDelete CFree GFree GerrorFree GSliceFree GSliceFreeSize GSliceDestroy
*
* Class SharedLockHandle is a version of the SharedHandle 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
* 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 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 handle is to be modified in one
* thread (by assigning to the handle 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 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).
*
* SharedLockHandle objects can be instantiated for pointers to
* constant objects (such as SharedLockHandle<const char*>), provided
* the deleter functor will take such pointers.
*
* This library provides StandardArrayDelete, CFree, GFree,
* GerrorFree, GSliceFree, GSliceFreeSize and GSliceDestroy deleter
* functors, which can be used as the second template parameter of the
* SharedLockHandle class. StandardArrayDelete is the default.
*
* As mentioned, by default glib atomic functions are used to provide
* thread-safe manipulation of the reference count. However, the
* symbol CGU_SHARED_LOCK_HANDLE_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_HANDLE_USE_MUTEX
* is to be defined, this is best done by textually amending the
* shared_handle.h header file before the library is compiled. This
* will ensure that everything in the program and the library which
* includes the shared_handle.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
*
* Although the semantics of std::shared_ptr in C++11 are not
* particularly suited to managing either arrays or C objects with
* accessor functions (such as in glib), most of the things that can
* be done by this class can be done by using std::shared_ptr with a
* specialised deleter. However, 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. The Cgu::SharedLockHandle class takes its deleter as a template
* parameter, which means that typedefs can be used to enable
* handles for particular deleters to be easily created (and as
* mentioned, this library provides a number of pre-formed deleter
* functors and typedefs for them). With std::shared_ptr, custom
* deleters must be passed to the shared_ptr constructor on every
* occasion a shared_ptr is constructed to manage a new object (and
* they cannot be templated as a typedef).
* 2. 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::SharedLockHandle allocation using glib memory slices can be
* achieved by compiling the library with the
* \--with-glib-memory-slices-no-compat configuration option.
* 3. 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::SharedLockHandle 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.
* 4. A user can explicitly state whether the shared handle 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::SharedHandle, and 'yes' in the case of
* Cgu::SharedLockHandle). 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::weak_ptr
* class, which Cgu::SharedLockHandle does not (there is a
* Cgu::GobjWeakHandle class, but that is cognate with Cgu::GobjHandle
* and is only usable with GObjects). In addition shared_ptr objects
* 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::SharedLockHandle 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 SharedLockHandle 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 SharedLockHandle objects.)
*/
template <class T, class Dealloc = StandardArrayDelete<T>> class SharedLockHandle {
Dealloc deleter;
#ifndef DOXYGEN_PARSING
struct RefItems {
#ifdef CGU_SHARED_LOCK_HANDLE_USE_MUTEX
Thread::Mutex* mutex_p;
unsigned int* ref_count_p;
#else
gint* ref_count_p;
#endif
T obj;
} ref_items;
#endif
// SharedLockHandle<T, Dealloc>::unreference() does not throw exceptions
// 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 SharedLockHandle object.
// Only the integer pointed to by it is shared amongst SharedLockHandle
// objects and requires locking
if (!ref_items.ref_count_p) return;
#ifdef CGU_SHARED_LOCK_HANDLE_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;
deleter(ref_items.obj);
}
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
deleter(ref_items.obj);
}
#endif
}
// SharedLockHandle<T, Dealloc>::reference() does not throw exceptions because
// Thread::Mutex::Lock::Lock() and Thread::Mutex::Lock::~Lock() do not throw
void reference() noexcept {
// we can (and should) check whether ref_items.ref_count_p is NULL without
// a lock, because that member is specific to this SharedLockHandle object.
// Only the integer pointed to by it is shared amongst SharedLockHandle
// objects and requires locking
if (!ref_items.ref_count_p) return;
#ifdef CGU_SHARED_LOCK_HANDLE_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 SharedLockHandle 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::SharedHandleAllocFail::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_handle.h can be textually amended before the
* library is compiled to define the symbol
* CGU_SHARED_LOCK_HANDLE_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 SharedLockHandle(T ptr = 0) {
if ((ref_items.obj = ptr)) { // not NULL
#ifdef CGU_SHARED_LOCK_HANDLE_USE_MUTEX
try {
ref_items.mutex_p = new Thread::Mutex;
}
catch (...) {
deleter(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;
deleter(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 (...) {
deleter(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_HANDLE_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 SharedLockHandle is to manage.
* @param tag Passing the tag emumerator
* Cgu::SharedHandleAllocFail::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::SharedHandleError will be
* thrown).
* @exception Cgu::SharedHandleError This constructor might throw
* Cgu::SharedHandleError 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::SharedHandleError 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_handle.h can be textually amended before the
* library is compiled to define the symbol
* CGU_SHARED_LOCK_HANDLE_USE_MUTEX so as to use mutexes instead,
* which might be useful for some debugging purposes. Were that to be
* done, Cgu::SharedHandleError 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).
*/
SharedLockHandle(T ptr, Cgu::SharedHandleAllocFail::Leave tag) {
if ((ref_items.obj = ptr)) { // not NULL
#ifdef CGU_SHARED_LOCK_HANDLE_USE_MUTEX
try {
ref_items.mutex_p = new Thread::Mutex;
}
catch (std::bad_alloc&) { // as we are not rethrowing, make NPTL friendly
throw SharedHandleError<T>(ptr);
}
catch (Thread::MutexError&) { // as we are not rethrowing, make NPTL friendly
throw SharedHandleError<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 SharedHandleError<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 SharedHandleError<T>(ptr);
}
# endif
#endif
}
else {
#ifdef CGU_SHARED_LOCK_HANDLE_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 SharedLockHandle to cease to manage its managed object
* (if any), deleting it if this is the last ShareLockHandle object
* managing it. If the argument passed is not NULL, the
* SharedLockHandle object will manage the new object passed (which
* must not be managed by any other SharedLockHandle object).
* @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::SharedHandleAllocFail::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_handle.h can be textually amended before the
* library is compiled to define the symbol
* CGU_SHARED_LOCK_HANDLE_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 SharedLockHandle 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
* SharedLockHandle object while another thread may be operating on,
* copying or dereferencing the same instance of SharedLockHandle. It
* is thread-safe as against another instance of SharedLockHandle
* managing the same object.
*/
void reset(T ptr = 0) {
SharedLockHandle tmp(ptr);
std::swap(ref_items, tmp.ref_items);
}
/**
* Causes the SharedLockHandle to cease to manage its managed object
* (if any), deleting it if this is the last ShareLockHandle object
* managing it. The SharedLockHandle object will manage the new
* object passed (which must not be managed by any other
* SharedLockHandle object). This method is exception safe, but see
* the comments below on Cgu::SharedHandleError.
* @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::SharedHandleAllocFail::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::SharedHandleError will be thrown).
* @exception Cgu::SharedHandleError This method might throw
* Cgu::SharedHandleError 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 (if any). Access to the object
* passed to the 'ptr' argument can be obtained via the thrown
* Cgu::SharedHandleError object.
* @note 1. A SharedLockHandle 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
* SharedLockHandle object while another thread may be operating on,
* copying or dereferencing the same instance of SharedLockHandle. It
* is thread-safe as against another instance of SharedLockHandle
* 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_handle.h can be textually amended before the
* library is compiled to define the symbol
* CGU_SHARED_LOCK_HANDLE_USE_MUTEX so as to use mutexes instead,
* which might be useful for some debugging purposes. Were that to be
* done, Cgu::SharedHandleError 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::SharedHandleAllocFail::Leave tag) {
SharedLockHandle tmp(ptr, tag);
std::swap(ref_items, tmp.ref_items);
}
/**
* The copy constructor does not throw.
* @param sh_hand The handle to be copied.
*/
SharedLockHandle(const SharedLockHandle& sh_hand) noexcept {
ref_items = sh_hand.ref_items;
reference();
}
/**
* The move constructor does not throw. It has move semantics.
* @param sh_hand The handle to be moved.
*/
SharedLockHandle(SharedLockHandle&& sh_hand) noexcept {
ref_items = sh_hand.ref_items;
#ifdef CGU_SHARED_LOCK_HANDLE_USE_MUTEX
sh_hand.ref_items.mutex_p = 0; // make sure the value is valid as we may assign it
#endif
sh_hand.ref_items.ref_count_p = 0;
sh_hand.ref_items.obj = 0;
}
/**
* This method (and so copy or move assignment) does not throw unless
* the destructor of a managed object throws.
* @param sh_hand the assignor.
* @return The SharedLockHandle 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
SharedLockHandle& operator=(SharedLockHandle sh_hand) {
std::swap(ref_items, sh_hand.ref_items);
return *this;
}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
T get() const noexcept {return ref_items.obj;}
/**
* This method does not throw.
* @return A pointer to the handled object (or NULL if none is
* handled).
*/
operator T() const noexcept {return ref_items.obj;}
/**
* This method does not throw.
* @return The number of SharedLockHandle objects referencing the
* managed object (or 0 if none is managed by this SharedLockHandle).
* @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 noexcept {
if (!ref_items.ref_count_p) return 0;
#ifdef CGU_SHARED_LOCK_HANDLE_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 handled
* object throws - that should never happen.
*/
~SharedLockHandle() {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, class Dealloc>
bool operator==(const SharedHandle<T, Dealloc>& s1, const SharedHandle<T, Dealloc>& s2) noexcept {
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, class Dealloc>
bool operator!=(const SharedHandle<T, Dealloc>& s1, const SharedHandle<T, Dealloc>& s2) noexcept {
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, class Dealloc>
bool operator<(const SharedHandle<T, Dealloc>& s1, const SharedHandle<T, Dealloc>& 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, class Dealloc>
bool operator==(const SharedLockHandle<T, Dealloc>& s1, const SharedLockHandle<T, Dealloc>& s2) noexcept {
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, class Dealloc>
bool operator!=(const SharedLockHandle<T, Dealloc>& s1, const SharedLockHandle<T, Dealloc>& s2) noexcept {
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, class Dealloc>
bool operator<(const SharedLockHandle<T, Dealloc>& s1, const SharedLockHandle<T, Dealloc>& 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 SharedHandle and SharedLockHandle objects to be
keys in unordered associative containers */
namespace std {
template <class T, class Dealloc>
struct hash<Cgu::SharedHandle<T, Dealloc>> {
typedef std::size_t result_type;
typedef Cgu::SharedHandle<T, Dealloc> 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, class Dealloc>
struct hash<Cgu::SharedLockHandle<T, Dealloc>> {
typedef std::size_t result_type;
typedef Cgu::SharedLockHandle<T, Dealloc> 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