From d951db4236146efd8f4f343740d50433739526c8 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 16 Dec 2014 11:29:03 +0000 Subject: [PATCH] gobject: Add g_set_object() convenience function to set GObject pointers MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- docs/reference/gobject/gobject-sections.txt | 1 + gobject/gobject.h | 63 +++++++++++++++++++++++++++++ gobject/tests/reference.c | 60 +++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt index 84eee38..c96a604 100644 --- a/docs/reference/gobject/gobject-sections.txt +++ b/docs/reference/gobject/gobject-sections.txt @@ -263,6 +263,7 @@ GParameter g_object_ref g_object_unref g_object_ref_sink +g_set_object g_clear_object GInitiallyUnowned GInitiallyUnownedClass diff --git a/gobject/gobject.h b/gobject/gobject.h index 522011f..40d3160 100644 --- a/gobject/gobject.h +++ b/gobject/gobject.h @@ -651,6 +651,69 @@ GLIB_AVAILABLE_IN_ALL void g_clear_object (volatile GObject **object_ptr); #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 { /**/ union { gpointer p; } priv; diff --git a/gobject/tests/reference.c b/gobject/tests/reference.c index 0742339..aefa1e8 100644 --- a/gobject/tests/reference.c +++ b/gobject/tests/reference.c @@ -152,6 +152,64 @@ test_clear_function (void) } 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 shouldn’t 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 shouldn’t 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 toggle_cb (gpointer data, GObject *obj, gboolean is_last) { gboolean *b = data; @@ -604,6 +662,8 @@ main (int argc, char **argv) g_test_add_func ("/type/class-private", test_class_private); g_test_add_func ("/object/clear", test_clear); 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/initially-unowned", test_initially_unowned); g_test_add_func ("/object/weak-pointer", test_weak_pointer); -- 2.7.4