gobject: Add g_set_object() convenience function to set GObject pointers

Along the same lines as g_clear_object(), g_set_object() is a
convenience function to update a GObject pointer, handling reference
counting transparently and correctly.

Specifically, it handles the case where a pointer is set to its current
value. If handled naïvely, that could result in the object instance
being finalised. In the following code, that happens when
(my_obj == new_value) and the object has a single reference:
    g_clear_object (&my_obj);
    my_obj = g_object_ref (new_value);

It also simplifies boilerplate code such as set_property()
implementations, which are otherwise long and boring.

Test cases included.

https://bugzilla.gnome.org/show_bug.cgi?id=741589
This commit is contained in:
Philip Withnall 2014-12-16 11:29:03 +00:00
parent 4f3ab40c04
commit d951db4236
3 changed files with 124 additions and 0 deletions

View File

@ -263,6 +263,7 @@ GParameter
g_object_ref g_object_ref
g_object_unref g_object_unref
g_object_ref_sink g_object_ref_sink
g_set_object
g_clear_object g_clear_object
GInitiallyUnowned GInitiallyUnowned
GInitiallyUnownedClass GInitiallyUnownedClass

View File

@ -651,6 +651,69 @@ GLIB_AVAILABLE_IN_ALL
void g_clear_object (volatile GObject **object_ptr); void g_clear_object (volatile GObject **object_ptr);
#define g_clear_object(object_ptr) g_clear_pointer ((object_ptr), g_object_unref) #define g_clear_object(object_ptr) g_clear_pointer ((object_ptr), g_object_unref)
/**
* g_set_object: (skip)
* @object_ptr: a pointer to a #GObject reference
* @new_object: (nullable) (transfer none): a pointer to the new #GObject to
* assign to it, or %NULL to clear the pointer
*
* Updates a #GObject pointer to refer to @new_object. It increments the
* reference count of @new_object (if non-%NULL), decrements the reference
* count of the current value of @object_ptr (if non-%NULL), and assigns
* @new_object to @object_ptr. The assignment is not atomic.
*
* @object_ptr must not be %NULL.
*
* A macro is also included that allows this function to be used without
* pointer casts. The function itself is static inline, so its address may vary
* between compilation units.
*
* One convenient usage of this function is in implementing property setters:
* |[
* void
* foo_set_bar (Foo *foo,
* Bar *new_bar)
* {
* g_return_if_fail (IS_FOO (foo));
* g_return_if_fail (new_bar == NULL || IS_BAR (new_bar));
*
* if (g_set_object (&foo->bar, new_bar))
* g_object_notify (foo, "bar");
* }
* ]|
*
* Returns: %TRUE if the value of @object_ptr changed, %FALSE otherwise
*
* Since: 2.44
*/
static inline gboolean
(g_set_object) (GObject **object_ptr,
GObject *new_object)
{
/* rely on g_object_[un]ref() to check the pointers are actually GObjects;
* elide a (object_ptr != NULL) check because most of the time we will be
* operating on struct members with a constant offset, so a NULL check would
* not catch bugs */
if (*object_ptr == new_object)
return FALSE;
if (new_object != NULL)
g_object_ref (new_object);
if (*object_ptr != NULL)
g_object_unref (*object_ptr);
*object_ptr = new_object;
return TRUE;
}
#define g_set_object(object_ptr, new_object) \
(/* Check types match. */ \
0 ? *(object_ptr) = (new_object), FALSE : \
(g_set_object) ((GObject **) (object_ptr), (GObject *) (new_object)) \
)
typedef struct { typedef struct {
/*<private>*/ /*<private>*/
union { gpointer p; } priv; union { gpointer p; } priv;

View File

@ -151,6 +151,64 @@ test_clear_function (void)
g_object_unref (tmp); g_object_unref (tmp);
} }
static void
test_set (void)
{
GObject *o = NULL;
GObject *tmp;
g_assert (!g_set_object (&o, NULL));
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert (g_set_object (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
/* Setting it again shouldnt cause finalisation. */
g_assert (!g_set_object (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert (g_set_object (&o, NULL));
g_assert (o == NULL);
g_assert (!G_IS_OBJECT (tmp)); /* finalised */
}
static void
test_set_function (void)
{
GObject *o = NULL;
GObject *tmp;
g_assert (!(g_set_object) (&o, NULL));
g_assert (o == NULL);
tmp = g_object_new (G_TYPE_OBJECT, NULL);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert ((g_set_object) (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 2);
g_object_unref (tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
/* Setting it again shouldnt cause finalisation. */
g_assert (!(g_set_object) (&o, tmp));
g_assert (o == tmp);
g_assert_cmpint (tmp->ref_count, ==, 1);
g_assert ((g_set_object) (&o, NULL));
g_assert (o == NULL);
g_assert (!G_IS_OBJECT (tmp)); /* finalised */
}
static void static void
toggle_cb (gpointer data, GObject *obj, gboolean is_last) toggle_cb (gpointer data, GObject *obj, gboolean is_last)
{ {
@ -604,6 +662,8 @@ main (int argc, char **argv)
g_test_add_func ("/type/class-private", test_class_private); g_test_add_func ("/type/class-private", test_class_private);
g_test_add_func ("/object/clear", test_clear); g_test_add_func ("/object/clear", test_clear);
g_test_add_func ("/object/clear-function", test_clear_function); g_test_add_func ("/object/clear-function", test_clear_function);
g_test_add_func ("/object/set", test_set);
g_test_add_func ("/object/set-function", test_set_function);
g_test_add_func ("/object/value", test_object_value); g_test_add_func ("/object/value", test_object_value);
g_test_add_func ("/object/initially-unowned", test_initially_unowned); g_test_add_func ("/object/initially-unowned", test_initially_unowned);
g_test_add_func ("/object/weak-pointer", test_weak_pointer); g_test_add_func ("/object/weak-pointer", test_weak_pointer);