gobj_handle.h
Go to the documentation of this file.
/* Copyright (C) 2005 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_GOBJ_HANDLE_H
#define CGU_GOBJ_HANDLE_H
#include <exception>
#include <functional> // for std::less and std::hash<T*>
#include <cstddef> // for std::size_t
#include <glib-object.h>
#include <c++-gtk-utils/cgu_config.h>
/**
* @addtogroup handles handles and smart pointers
*/
namespace Cgu {
/**
* @class GobjHandle gobj_handle.h c++-gtk-utils/gobj_handle.h
* @brief This is a handle for managing the reference count of
* GObjects.
* @ingroup handles
* @sa MainWidgetBase GobjWeakHandle
*
* This is a class which manages the reference count of GObjects. It
* does not maintain its own reference count, but interfaces with that
* kept by the glib object system.
*
* GobjHandles are most useful to manage GObjects which are not also
* GtkWidgets or GInitiallyUnowned objects - GtkWidgets and
* GInitiallyUnowned objects have initial floating references which
* will result in them being automatically managed by the container in
* which they are held. Nonetheless, GobjHandles can be used to hold
* GtkWidgets and GInitiallyUnowned objects, as the constructor of a
* GobjHandle which takes a pointer will automatically take ownership
* of a newly created GtkWidget or GInitiallyUnowned object, by
* calling g_object_ref_sink(). Plain GObjects do not need to be sunk
* to be owned by the GobjHandle.
*
* Note that g_object_ref_sink() is not called by the constructor
* taking a pointer if the floating reference has already been sunk,
* so if that constructor is passed an object already owned by a GTK+
* container it will be necessary to call g_object_ref() on it
* explicitly. This behaviour will ensure that the handle behaves the
* same whether it is holding a plain GObject, or it is holding a
* GInitiallyUnowned/GtkWidget object. Generally however, where an
* object is already owned by a container, the object should be passed
* by another handle - ie by the copy constructor (or by the
* assignment operator), as those always increment the reference count
* automatically.
*
* In other words, invoke the constructor taking a pointer only with a
* newly created object (whether a GObject, GInitiallyUnowned or
* GtkWidget object), and everything else will take care of itself. In
* this respect, GobjHandles work the same way as conventional shared
* pointer implementations managing objects allocated on free store.
* The same applies to the reset() method. (Care is required however
* if initializing a Cgu::GobjHandle with a widget or GObject object
* obtained from GtkBuilder, since these are not "newly created" in
* this sense. It is necessary to call g_object_ref() by hand in that
* case, since GtkBuilder does not by itself pass ownership of any
* objects it creates.)
*
* Because any GTK+ containers themselves increment the reference
* count of a GObject or GtkWidget where they need to take ownership,
* an already-managed object held by a GobjHandle can safely be passed
* by pointer to a GTK+ container, and that is one of the intended
* usages. For that purpose, although the class has operator*() and
* operator->() dereferencing operators, and so has normal smart
* pointer functionality, as it is intended for use with the normal C
* GObject/pango/GTK+ interfaces, ordinary use would involve passing
* the handle to a function taking a pointer by means of the
* operatorT*() type conversion operator (which returns the underlying
* pointer), or by explicitly calling the get() method to obtain the
* underlying pointer.
*
* The principal intended usage of GobjHandle is to automatically
* handle GObject reference counts and therefore to make GObjects
* exception-safe, but they also permit GObjects/GtkWidgets to be kept
* in standard C++ containers.
*
* As of glib-2.8, g_object_ref() and g_object_unref() are thread
* safe, so with glib-2.8 or greater there can be different GobjHandle
* instances in different threads referencing the same GObject object.
* Of course, if that is done, this does not affect the need (or
* otherwise) in the particular use in question to lock anything other
* than the reference count - say when accessing the referenced object
* itself in different threads.
*
* Typical usage might be, for example, as follows:
*
* @code
* using namespace Cgu;
* GobjHandle<GtkListStore> store(gtk_list_store_new(1, G_TYPE_STRING));
*
* [ ... fill the list store ... ]
*
* GobjHandle<GtkWidget> view(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store.get()));
* // 'view' will take sole ownership of the list store when 'store' goes out of scope, or
* // 'store' could be kept alive so that the list store will survive removal from the view
*
* [ ... set up an interface including a GtkVBox 'vbox' which will hold the tree view ... ]
*
* gtk_container_add(GTK_CONTAINER(vbox), view);
* // 'vbox' will take sole ownership of the tree view when 'view' goes out of scope, or
* // 'view' could be kept alive so that the tree view will survive removal from the vbox
* @endcode
*/
template <class T> class GobjHandle {
T* obj_p;
void unreference() {
if (obj_p) g_object_unref(obj_p);
}
void reference() {
if (obj_p) g_object_ref(obj_p);
}
public:
/**
* The constructor does not throw. g_object_ref_sink() is called if
* the managed object has a floating reference.
* @param ptr The object which the GobjHandle is to manage (if any).
* @note The object passed should not normally be already owned by a
* GTK+ container or managed by any other GobjHandle object. If it
* is, g_object_ref() must be called explicitly by the user code.
*/
explicit GobjHandle(T* ptr = 0) {
obj_p = ptr;
// if an object with a floating reference has been passed to this constructor,
// take ownership of it
if (ptr && g_object_is_floating(ptr)) {
g_object_ref_sink(ptr);
}
}
/**
* Causes the handle to cease to manage its managed object (if any)
* and decrements its reference count, so destroying it if the
* reference count thereby becomes 0. If the argument passed is not
* NULL, the handle will manage the new object passed and
* g_object_ref_sink() is called if the new object has a floating
* reference. This method does not throw.
* @param ptr NULL (the default), or a new object to manage.
* @note The new object passed should not normally be already owned
* by a GTK+ container or managed by any other GobjHandle object. If
* it is, g_object_ref() must be called explicitly by the user code.
*/
void reset(T* ptr = 0) {
unreference();
obj_p = ptr;
// if an object with a floating reference has been passed to this method,
// take ownership of it
if (ptr && g_object_is_floating(ptr)) {
g_object_ref_sink(ptr);
}
}
/**
* The copy constructor does not throw. It increments the reference
* count of the managed object.
* @param gobj The handle to be copied.
*/
GobjHandle(const GobjHandle& gobj) {
obj_p = gobj.obj_p;
reference();
}
/**
* The move constructor does not throw. It has move semantics.
* @param gobj The handle to be moved.
*/
GobjHandle(GobjHandle&& gobj) {
obj_p = gobj.obj_p;
gobj.obj_p = 0;
}
// We don't have a constructor for GobjHandle taking a GobjWeakHandle
// object. If we did that, we would have to remove the GobjWeakHandle
// constructor taking a pointer so we know that its tracked object
// always has an owner when initialising a new GobjHandle with the
// GobjWeakHandle, so we can in turn know we can increase the reference
// count when initialising the GobjHandle. However, removing that
// constructor would be inconsistent with one of the purposes of having
// a GobjWeakHandle class. For the same reason, we don't have an
// assignment operator for GobjHandle taking such an object.
/**
* This method does not throw. It decrements the reference count of
* the former managed object (if any), so destroying it if the
* reference count thereby becomes 0, and increments the reference
* count of the new managed object.
* @param gobj The assignor.
* @return The GobjHandle object after assignment.
*/
GobjHandle& operator=(const GobjHandle& gobj) {
// check whether we are already referencing this object -
// if so make this a null op. This will also deal with
// self-assignment
if (obj_p != gobj.obj_p) {
// first unreference any object referenced by this handle
unreference();
// now inherit the GObject from the assigning handle
// and reference it
obj_p = gobj.obj_p;
reference();
}
return *this;
}
/**
* This method does not throw. It decrements the reference count of
* the former managed object (if any), so destroying it if the
* reference count thereby becomes 0, and has move semantics with
* respect to the new managed object.
* @param gobj The handle to be moved.
* @return The GobjHandle object after the move operation.
*/
GobjHandle& operator=(GobjHandle&& gobj) {
// check for self-assignment
if (this != &gobj) {
// first unreference any object referenced by this handle
unreference();
// now inherit the GObject from the moving handle
obj_p = gobj.obj_p;
gobj.obj_p = 0;
}
return *this;
}
/**
* This method does not throw.
* @return A pointer to the handled GObject (or NULL if none is
* handled).
*/
T* get() const {return obj_p;}
/**
* This method does not throw.
* @return A reference to the handled GObject.
*/
T& operator*() const {return *obj_p;}
/**
* This method does not throw.
* @return A pointer to the handled GObject (or NULL if none is
* handled).
*/
T* operator->() const {return obj_p;}
/**
* This method does not throw.
* @return A pointer to the handled GObject (or NULL if none is
* handled).
*/
operator T*() const {return obj_p;}
/**
* The destructor does not throw. It decrements the reference count
* of the managed object (if any), so destroying it if the reference
* count thereby becomes 0.
*/
~GobjHandle() {unreference();}
};
/**
* @class GobjWeakHandle gobj_handle.h c++-gtk-utils/gobj_handle.h
* @brief This is a handle for managing weak references to GObjects.
* @ingroup handles
* @sa GobjHandle
*
* This class tracks a GObject, so that if that GObject no longer
* exists then operator bool() or the valid() method will return
* false, but does not take a strong reference by incrementing the
* reference count to the GObject and so take ownership of it. It has
* two main use areas: first, in order to break reference cycles that
* may otherwise arise if two classes would otherwise hold strong
* references to each other. Secondly, to manage a pointer to a
* GObject returned by a GTK+ getter function where ownership is not
* passed (that is, where the user is not expected to call
* g_object_unref() when finished with the return value). A typical
* example of this is the GtkTreeSelection object returned by
* gtk_tree_view_get_selection(). The GtkTreeSelection object is part
* of the GtkTreeView's implemention and will become invalid as soon
* as the GtkTreeView object is finalized.
*
* As in the case of the GobjHandle class, although this class has
* operator*() and operator->() dereferencing operators, and so has
* normal smart pointer functionality, as it is intended for use with
* the normal C GObject/pango/GTK+ interfaces, ordinary use would
* involve passing the handle to a function taking a pointer by means
* of the operatorT*() type conversion operator (which returns the
* underlying pointer), or by explicitly calling the get() method to
* obtain the underlying pointer.
*
* Typical usage is as follows:
*
* @code
* using namespace Cgu;
* GobjWeakHandle<GtkTreeSelection> s(gtk_tree_view_get_selection(tree_view));
* gtk_tree_selection_set_mode(s, GTK_SELECTION_SINGLE);
* ...
*
* [some code blocks later]
* if (s) { // check that the GtkTreeSelection object still exists.
* GtkTreeIter iter;
* GtkTreeModel* model = 0;
* gtk_tree_selection_get_selected(s, &model, &iter);
* ...
* }
* else [report error];
* @endcode
*
* Or instead of an 'if' block, GobjWeakHandleError could be caught:
*
* @code
* using namespace Cgu;
* GobjWeakHandle<GtkTreeSelection> s(gtk_tree_view_get_selection(tree_view));
* gtk_tree_selection_set_mode(s, GTK_SELECTION_SINGLE);
* ...
*
* [some code blocks later]
* GtkTreeIter iter;
* GtkTreeModel* model = 0;
* try {
* gtk_tree_selection_get_selected(s, &model, &iter);
* ...
* }
* catch (GobjWeakHandleError&) {[report error]}
* @endcode
*
* @b Thread-safe @b use
*
* This class wraps
* g_object_add_weak_pointer()/g_object_remove_weak_pointer(), and as
* those GObject functions have practical limitations concerning
* thread-safe use, this class has the same practical limitations. As
* shown above, typical usage for a weak pointer 's' would be 'if (s)
* do_it(s)', but if the thread calling that sequence (thread A) were
* not the thread controlling the lifetime of the referenced GObject
* (thread B), then thread B may have destroyed the GObject between
* thread A testing 's' and then calling the referenced object. The
* same applies to the test leading to GobjWeakHandleError being
* thrown.
*
* In other words, in the GtkTreeSelection code example above, if the
* thread calling gtk_tree_selection_get_selected() were not the main
* GUI thread (which would anyway require the use of
* gdk_threads_enter()/gdk_threads_leave()), then the calling thread
* must ensure that the main GUI thread does not destroy the relevant
* tree view, and so the GtkTreeSelection object, from the beginning
* of the 'if' test to the end of the 'if' block, or for the duration
* of the try block. (This cannot be done just by incrementing the
* reference count of the tree view or the tree selection in the
* calling thread before the 'if' test or the try block is entered,
* because by the time the reference is incremented and the weak
* pointer tested, the tree view and tree selection may already be in
* their dispose functions but the tree selection's dispose function
* may not yet have reached the point of dispatching the callback
* NULLing the weak pointer. As a general design issue, it is usually
* best only to call GTK+ functions in one thread, and in order to
* make that straightforward, this library contains a number of
* classes and functions for inter-thread communication.)
*/
struct GobjWeakHandleError: public std::exception {
virtual const char* what() const throw() {return "GobjWeakHandleError\n";}
};
template <class T> class GobjWeakHandle {
T* obj_p;
public:
/**
* This constructor does not throw.
* @param ptr The object which the GobjWeakHandle is to track (if any).
*/
explicit GobjWeakHandle(T* ptr = 0) {
obj_p = ptr;
if (ptr) g_object_add_weak_pointer((GObject*)ptr,
(void**)&obj_p);
}
/**
* Causes the handle to cease to track its tracked object (if any).
* If the argument passed is not NULL, the handle will track the new
* object passed. This method does not throw.
* @param ptr NULL (the default), or a new object to track.
*/
void reset(T* ptr = 0) {
if (obj_p) g_object_remove_weak_pointer((GObject*)obj_p,
(void**)&obj_p);
obj_p = ptr;
if (ptr) g_object_add_weak_pointer((GObject*)ptr,
(void**)&obj_p);
}
/**
* The copy constructor does not throw. It constructs a new weak
* pointer tracking the same GObject as that tracked by the existing
* weak pointer.
* @param gobj The handle to be copied.
*/
GobjWeakHandle(const GobjWeakHandle& gobj) {
obj_p = gobj.obj_p;
if (obj_p) g_object_add_weak_pointer((GObject*)obj_p,
(void**)&obj_p);
}
/**
* This constructor constructs a weak pointer for a GObject managed
* by a GobjHandle handle. It does not throw.
* @param gobj The GobjHandle managing the GObject which the
* GobjWeakHandle is to track.
*/
GobjWeakHandle(const GobjHandle<T>& gobj) {
obj_p = gobj.get();
if (obj_p) g_object_add_weak_pointer((GObject*)obj_p,
(void**)&obj_p);
}
/**
* This method does not throw. It causes the handle to cease to
* track its tracked object (if any), and begin tracking the same
* GObject as that tracked by the assignor. This method does not
* throw.
* @param gobj The assignor.
* @return The GobjWeakHandle object after assignment.
*/
GobjWeakHandle& operator=(const GobjWeakHandle& gobj) {
// self assignment takes care of itself
reset(gobj.obj_p);
return *this;
}
/**
* This method does not throw. It causes the handle to cease to
* track its tracked object (if any), and begin tracking the GObject
* managed by the GobjHandle argument. This method does not throw.
* @param gobj The assignor GobjHandle.
* @return The GobjWeakHandle object after assignment.
*/
GobjWeakHandle& operator=(const GobjHandle<T>& gobj) {
reset(gobj.get());
return *this;
}
/**
* This method does not throw.
* @return True if the tracked GObject still exists, or false if it
* does not or no GObject is being tracked.
* @note The valid() method is a synonym for this method.
*/
operator bool() const {return obj_p;}
#ifndef DOXYGEN_PARSING
/*
* DEPRECATED: Do not use this method as it is only retained for
* source compatibility. Its semantics are the wrong way around:
* expired() returns true if the GObject still exists, instead of
* false. Use operator bool() or the valid() method instead, which
* have the correct semantics. This method does not throw.
*/
bool expired() const {return obj_p;}
#endif
/**
* This method does not throw.
* @return True if the tracked GObject still exists, or false if it
* does not or no GObject is being tracked.
* @note operator bool() is a synonym for this method.
*
* Since 2.0.5
*/
bool valid() const {return obj_p;}
/**
* This method does not throw.
* @return A pointer to the tracked GObject.
* @exception GobjWeakHandleError This method will throw
* GobjWeakHandleError if the tracked object no longer exists or none
* is being tracked. There is no need to check for this exception if
* the status of the tracked object has been established with
* operator bool() or valid().
*/
T* get() const {if (!obj_p) throw GobjWeakHandleError(); return obj_p;}
/**
* This method does not throw.
* @return A reference to the tracked GObject.
* @exception GobjWeakHandleError This method will throw
* GobjWeakHandleError if the tracked object no longer exists or none
* is being tracked. There is no need to check for this exception if
* the status of the tracked object has been established with
* operator bool() or valid().
*/
T& operator*() const {if (!obj_p) throw GobjWeakHandleError(); return *obj_p;}
/**
* This method does not throw.
* @return A pointer to the tracked GObject.
* @exception GobjWeakHandleError This method will throw
* GobjWeakHandleError if the tracked object no longer exists or none
* is being tracked. There is no need to check for this exception if
* the status of the tracked object has been established with
* operator bool() or valid().
*/
T* operator->() const {if (!obj_p) throw GobjWeakHandleError(); return obj_p;}
/**
* This method does not throw.
* @return A pointer to the tracked GObject.
* @exception GobjWeakHandleError This method will throw
* GobjWeakHandleError if the tracked object no longer exists or none
* is being tracked. There is no need to check for this exception if
* the status of the tracked object has been established with
* operator bool() or valid().
*/
operator T*() const {if (!obj_p) throw GobjWeakHandleError(); return obj_p;}
/**
* The destructor does not throw.
*/
~GobjWeakHandle() {if (obj_p) g_object_remove_weak_pointer((GObject*)obj_p,
(void**)&obj_p);}
};
#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 GobjHandle<T>& h1, const GobjHandle<T>& h2) {
return (h1.get() == h2.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 GobjHandle<T>& h1, const GobjHandle<T>& h2) {
return !(h1 == h2);
}
// 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 GobjHandle<T>& h1, const GobjHandle<T>& h2) {
return std::less<T*>()(h1.get(), h2.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 GobjHandle objects to be keys in unordered
associative containers */
namespace std {
template <class T>
struct hash<Cgu::GobjHandle<T>> {
typedef std::size_t result_type;
typedef Cgu::GobjHandle<T> argument_type;
result_type operator()(const argument_type& h) 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*>()(h.get());
}
};
} // namespace std
#endif // CGU_USE_SMART_PTR_COMPARISON
#endif