intrusive_ptr.h
Go to the documentation of this file.
/* Copyright (C) 2006 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_INTRUSIVE_PTR_H
#define CGU_INTRUSIVE_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
// IntrusiveLockCounter class
/* #define CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX 1 */
#include <utility> // for std::move and std::swap
#include <functional> // for std::less and std::hash<T*>
#include <cstddef> // for std::size_t
#ifdef CGU_INTRUSIVE_LOCK_COUNTER_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 IntrusivePtr intrusive_ptr.h c++-gtk-utils/intrusive_ptr.h
* @brief This is a smart pointer for managing objects allocated on
* freestore which maintain their own reference count.
* @ingroup handles
*
* @details This is a class which manages objects which maintain their
* own reference count. It requires that the referenced object has
* two functions called ref() and unref(), which increment and
* decrement the reference count respectively. The IntrusiveCounter
* or IntrusiveLockCounter class can be inherited from to do this, but
* they do not have to be used. The IntrusiveLockCounter class is the
* same as the IntrusiveCounter class, except that it locks the
* reference count when it is incremented or decremented in order that
* IntrusivePtr objects in different threads can access the same
* object. (But only the reference count is locked, not the methods
* of the referenced object.)
*
* All the constructors (including the constructor which takes a raw
* pointer) increment the reference count, and the destructor
* decrements it and expects the referenced object to be deleted when
* the last IntrusivePtr referencing a particular object is destroyed.
* The IntrusiveCounter and IntrusiveLockCounter classes behave in
* this way. (This is different from the behaviour of GobjHandle
* smart pointers, which are constrained by the GObject reference
* counting system which begins with a reference count of 1 rather
* than 0, and of course different from normal shared pointer
* implementations for the same reason. The advantage of the approach
* with IntrusivePtr is that an already-managed object may safely be
* passed to the constructor taking a raw pointer without any
* additional steps being necessary.)
*/
template <class T> class IntrusivePtr {
T* obj_p;
void unreference() {
if (obj_p) obj_p->unref();
}
void reference() {
if (obj_p) obj_p->ref();
}
public:
/**
* This constructor does not throw.
* @param ptr The object which the IntrusivePtr is to manage (if
* any).
*/
explicit IntrusivePtr(T* ptr = 0) {
obj_p = ptr;
reference();
}
/**
* This copy constructor does not throw.
* @param intr_ptr The intrusive pointer to be copied.
*/
IntrusivePtr(const IntrusivePtr& intr_ptr) {
obj_p = intr_ptr.obj_p;
reference();
}
/**
* The move constructor does not throw. It has move semantics.
* @param intr_ptr The instrusive pointer to be moved.
*/
IntrusivePtr(IntrusivePtr&& intr_ptr) {
obj_p = intr_ptr.obj_p;
intr_ptr.obj_p = 0;
}
template <class U> friend class IntrusivePtr;
/**
* 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 intr_ptr The intrusive pointer to be copied.
*/
template <class U> IntrusivePtr(const IntrusivePtr<U>& intr_ptr) {
obj_p = intr_ptr.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 intr_ptr The intrusive pointer to be moved.
*/
template <class U> IntrusivePtr(IntrusivePtr<U>&& intr_ptr) {
obj_p = intr_ptr.obj_p;
intr_ptr.obj_p = 0;
}
/**
* This method (and so copy or move assignment) does not throw unless
* the destructor of a managed object throws.
* @param intr_ptr The assignee.
* @return The IntrusivePtr 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
IntrusivePtr& operator=(IntrusivePtr intr_ptr) {
std::swap(obj_p, intr_ptr.obj_p);
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 intr_ptr The assignee.
* @return The IntrusivePtr object after assignment.
*/
template <class U> IntrusivePtr& operator=(const IntrusivePtr<U>& intr_ptr) {
return operator=(IntrusivePtr(intr_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 intr_ptr The intrusive pointer to be moved.
* @return The IntrusivePtr object after the move operation.
*/
template <class U> IntrusivePtr& operator=(IntrusivePtr<U>&& intr_ptr) {
return operator=(IntrusivePtr(std::move(intr_ptr)));
}
/**
* This method does not throw.
* @return A pointer to the managed object (or NULL if none is
* handled).
*/
T* get() const {return obj_p;}
/**
* This method does not throw.
* @return A reference to the managed object.
*/
T& operator*() const {return *obj_p;}
/**
* This method does not throw.
* @return A pointer to the managed object (or NULL if none is
* handled).
*/
T* operator->() const {return obj_p;}
/**
* Causes the intrusive pointer to cease to manage its managed
* object, deleting it if this is the last intrusive pointer managing
* it. If the argument passed is not NULL, the intrusive pointer
* will manage the new object passed. This method does not throw
* unless the destructor of a managed object throws.
* @param ptr NULL (the default), or a new object to manage.
*/
void reset(T* ptr = 0) {
IntrusivePtr tmp(ptr);
std::swap(obj_p, tmp.obj_p);
}
/**
* The destructor does not throw unless the destructor of a managed
* object throws - that should never happen.
*/
~IntrusivePtr() {unreference();}
};
/**
* @class IntrusiveCounter intrusive_ptr.h c++-gtk-utils/intrusive_ptr.h
* @brief This is a counter class providing the ref() and unref()
* functions required by IntrusivePtr.
* @ingroup handles
* @sa IntrusiveLockCounter.
*
* This is a counter class providing the ref() and unref() functions
* required by IntrusivePtr. It is intended to be inherited from by
* classes which are to be managed by such a smart pointer.
*/
class IntrusiveCounter {
unsigned int count;
public:
// we should not be able to copy objects of this class
// - objects, if constructed on the heap, should be passed
// via an IntrusivePtr object. An object of a derived class
// might still copy itself via its copy constructor and take
// along a new base IntrusiveCounter object constructed via
// the default constructor, and thus have a correct reference
// count of 0, but derived classes should not try to provide
// their own assignment operators.
/**
* This class cannot be copied. The copy constructor is deleted.
*/
IntrusiveCounter(const IntrusiveCounter&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*/
IntrusiveCounter& operator=(const IntrusiveCounter&) = delete;
/**
* Increments the reference count. This method does not throw.
*/
void ref() {++count;}
/**
* Decrements the reference count, and if the count reaches 0 deletes
* itself (ie the managed object). This method does not throw unless
* the destructor of a derived class throws - that should never
* happen.
*/
void unref() {
--count;
if (count == 0) delete this;
}
IntrusiveCounter(): count(0) {}
virtual ~IntrusiveCounter() {}
};
/**
* @class IntrusiveLockCounter intrusive_ptr.h c++-gtk-utils/intrusive_ptr.h
* @brief This is a counter class providing the ref() and unref()
* functions required by IntrusivePtr, with a thread safe reference
* count..
* @ingroup handles
* @sa IntrusiveCounter.
*
* This is a counter class providing the ref() and unref() functions
* required by IntrusivePtr. It is intended to be inherited from by
* classes which are to be managed by such a smart pointer, and
* includes synchronization so that such an inheriting class object
* can be accessed by different IntrusivePtr objects in different
* 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.
*
* As mentioned, by default glib atomic functions are used to provide
* thread-safe manipulation of the reference count. However, the
* symbol CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX can be defined so that
* the library uses mutexes instead, which might be useful for some
* debugging purposes. Note that if
* CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX is to be defined, this is best
* done by textually amending the intrusive_ptr.h header file before
* the library is compiled. This will ensure that everything in the
* program and the library which includes the intrusive_ptr.h header
* is guaranteed to see the same definitions so that the C++
* standard's one-definition-rule is complied with.
*/
class IntrusiveLockCounter {
#ifdef CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX
unsigned int count;
Thread::Mutex mutex;
#else
gint count;
#endif
public:
// we should not be able to copy objects of this class
// - objects, if constructed on the heap, should be passed
// via an IntrusivePtr object. An object of a derived class
// might still copy itself via its copy constructor and take
// along a new base IntrusiveLockCounter object constructed via
// the default constructor, and thus have a correct reference
// count of 0, but derived classes should not try to provide
// their own assignment operators.
/**
* This class cannot be copied. The copy constructor is deleted.
*/
IntrusiveLockCounter(const IntrusiveLockCounter&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*/
IntrusiveLockCounter& operator=(const IntrusiveLockCounter&) = delete;
/**
* Increments the reference count. This method does not throw.
*/
void ref() {
#ifdef CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX
Thread::Mutex::Lock lock(mutex);
++count;
#else
g_atomic_int_inc(&count);
#endif
}
/**
* Decrements the reference count, and if the count reaches 0 deletes
* itself (ie the managed object). This method does not throw unless
* the destructor of a derived class throws - that should never
* happen.
*/
void unref() {
#ifdef CGU_INTRUSIVE_LOCK_COUNTER_USE_MUTEX
mutex.lock();
--count;
if (count == 0) {
mutex.unlock();
delete this;
}
else mutex.unlock();
#else
if (g_atomic_int_dec_and_test(&count)) {
delete this;
}
#endif
}
/**
* By default, glib atomic functions are used to provide thread-safe
* manipulation of the reference count. However, the header file
* intrusive_ptr.h can be textually amended before the library is
* compiled to define the symbol CGU_INTRUSIVE_LOCK_COUNTER_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.
*
* Otherwise, this constructor does not throw.
*/
IntrusiveLockCounter(): count(0) {}
/**
* This destructor does not throw, unless the destructor of a derived
* class throws.
*/
virtual ~IntrusiveLockCounter() {}
};
#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 IntrusivePtr<T>& s1, const IntrusivePtr<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 IntrusivePtr<T>& s1, const IntrusivePtr<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. It compares the addresses
* of the managed objects.
*
* Since 2.0.0-rc2
*/
template <class T>
bool operator<(const IntrusivePtr<T>& s1, const IntrusivePtr<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)
/* This struct allows InstrusivePtr objects to be keys in unordered
associative containers */
namespace std {
template <class T>
struct hash<Cgu::IntrusivePtr<T>> {
typedef std::size_t result_type;
typedef Cgu::IntrusivePtr<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