From 156d32cb8039f2b3df4af49550aaea4b9701d072 Mon Sep 17 00:00:00 2001 From: Martin Blanchard Date: Wed, 20 Dec 2017 23:05:53 +0100 Subject: [PATCH] gobject: new g_set_weak_pointer() & g_clear_weak_pointer() helpers Weak-pointers are currently lacking g_set_object() & g_clear_object() helpers equivalent. New functions (and macros, both are provided) are convenient in many case, especially for the property's notify-on-set pattern: if (g_set_weak_pointer (...)) g_object_notify (...) Inspired by Christian Hergert's original implementation for gnome-builder. https://bugzilla.gnome.org/show_bug.cgi?id=749527 --- docs/reference/gobject/gobject-sections.txt | 2 + gobject/gobject.h | 102 +++++++++++++++++++ gobject/tests/reference.c | 106 ++++++++++++++++++++ 3 files changed, 210 insertions(+) diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt index 2f4b30079..d1fc4bdc1 100644 --- a/docs/reference/gobject/gobject-sections.txt +++ b/docs/reference/gobject/gobject-sections.txt @@ -279,6 +279,8 @@ g_object_weak_ref g_object_weak_unref g_object_add_weak_pointer g_object_remove_weak_pointer +g_set_weak_pointer +g_clear_weak_pointer GToggleNotify g_object_add_toggle_ref g_object_remove_toggle_ref diff --git a/gobject/gobject.h b/gobject/gobject.h index eaefe9243..1a6c96915 100644 --- a/gobject/gobject.h +++ b/gobject/gobject.h @@ -739,6 +739,108 @@ static inline gboolean (g_set_object) ((GObject **) (object_ptr), (GObject *) (new_object)) \ ) +/** + * g_clear_weak_pointer: (skip) + * @weak_pointer_location: The memory address of a pointer + * + * Clears a weak reference to a #GObject. + * + * @weak_pointer_location must not be %NULL. + * + * If the weak reference is %NULL then this function does nothing. + * Otherwise, the weak reference to the object is removed for that location + * and the pointer is set to %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. + * + * Since: 2.56 + */ +static inline void +(g_clear_weak_pointer) (gpointer *weak_pointer_location) +{ + GObject *object = (GObject *) *weak_pointer_location; + + if (object != NULL) + { + g_object_remove_weak_pointer (object, weak_pointer_location); + *weak_pointer_location = NULL; + } +} + +#define g_clear_weak_pointer(weak_pointer_location) \ + (/* Check types match. */ \ + (g_clear_weak_pointer) ((gpointer *) (weak_pointer_location)) \ + ) + +/** + * g_set_weak_pointer: (skip) + * @weak_pointer_location: the memory address of a pointer + * @new_object: (nullable) (transfer none): a pointer to the new #GObject to + * assign to it, or %NULL to clear the pointer + * + * Updates a pointer to weakly refer to @new_object. It assigns @new_object + * to @weak_pointer_location and ensures that @weak_pointer_location will + * automaticaly be set to %NULL if @new_object gets destroyed. The assignment + * is not atomic. The weak reference is not thread-safe, see + * g_object_add_weak_pointer() for details. + * + * @weak_pointer_location 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_weak_pointer (&foo->bar, new_bar)) + * g_object_notify (foo, "bar"); + * } + * ]| + * + * Returns: %TRUE if the value of @weak_pointer_location changed, %FALSE otherwise + * + * Since: 2.56 + */ +static inline gboolean +(g_set_weak_pointer) (gpointer *weak_pointer_location, + GObject *new_object) +{ + GObject *old_object = (GObject *) *weak_pointer_location; + + /* elide a (weak_pointer_location != 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 (old_object == new_object) + return FALSE; + + if (old_object != NULL) + g_object_remove_weak_pointer (old_object, weak_pointer_location); + + *weak_pointer_location = new_object; + + if (new_object != NULL) + g_object_add_weak_pointer (new_object, weak_pointer_location); + + return TRUE; +} + +#define g_set_weak_pointer(weak_pointer_location, new_object) \ + (/* Check types match. */ \ + 0 ? *(weak_pointer_location) = (new_object), FALSE : \ + (g_set_weak_pointer) ((gpointer *) (weak_pointer_location), (GObject *) (new_object)) \ + ) + typedef struct { /**/ union { gpointer p; } priv; diff --git a/gobject/tests/reference.c b/gobject/tests/reference.c index aefa1e894..e3f86315c 100644 --- a/gobject/tests/reference.c +++ b/gobject/tests/reference.c @@ -313,6 +313,108 @@ test_weak_pointer (void) g_assert (weak2 == obj); } +static void +test_weak_pointer_clear (void) +{ + GObject *obj; + gpointer weak = NULL; + + g_clear_weak_pointer (&weak); + g_assert_null (weak); + + weak = obj = g_object_new (G_TYPE_OBJECT, NULL); + g_assert_cmpint (obj->ref_count, ==, 1); + + g_object_add_weak_pointer (obj, &weak); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + g_clear_weak_pointer (&weak); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_null (weak); + + g_object_unref (obj); +} + +static void +test_weak_pointer_clear_function (void) +{ + GObject *obj; + gpointer weak = NULL; + + (g_clear_weak_pointer) (&weak); + g_assert_null (weak); + + weak = obj = g_object_new (G_TYPE_OBJECT, NULL); + g_assert_cmpint (obj->ref_count, ==, 1); + + g_object_add_weak_pointer (obj, &weak); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + (g_clear_weak_pointer) (&weak); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_null (weak); + + g_object_unref (obj); +} + +static void +test_weak_pointer_set (void) +{ + GObject *obj; + gpointer weak = NULL; + + g_assert_false (g_set_weak_pointer (&weak, NULL)); + g_assert_null (weak); + + obj = g_object_new (G_TYPE_OBJECT, NULL); + g_assert_cmpint (obj->ref_count, ==, 1); + + g_assert_true (g_set_weak_pointer (&weak, obj)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + g_assert_true (g_set_weak_pointer (&weak, NULL)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_null (weak); + + g_assert_true (g_set_weak_pointer (&weak, obj)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + g_object_unref (obj); + g_assert_null (weak); +} + +static void +test_weak_pointer_set_function (void) +{ + GObject *obj; + gpointer weak = NULL; + + g_assert_false ((g_set_weak_pointer) (&weak, NULL)); + g_assert_null (weak); + + obj = g_object_new (G_TYPE_OBJECT, NULL); + g_assert_cmpint (obj->ref_count, ==, 1); + + g_assert_true ((g_set_weak_pointer) (&weak, obj)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + g_assert_true ((g_set_weak_pointer) (&weak, NULL)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_null (weak); + + g_assert_true ((g_set_weak_pointer) (&weak, obj)); + g_assert_cmpint (obj->ref_count, ==, 1); + g_assert_true (weak == obj); + + g_object_unref (obj); + g_assert_null (weak); +} + /* See gobject/tests/threadtests.c for the threaded version */ static void test_weak_ref (void) @@ -667,6 +769,10 @@ main (int argc, char **argv) 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); + g_test_add_func ("/object/weak-pointer/clear", test_weak_pointer_clear); + g_test_add_func ("/object/weak-pointer/clear-function", test_weak_pointer_clear_function); + g_test_add_func ("/object/weak-pointer/set", test_weak_pointer_set); + g_test_add_func ("/object/weak-pointer/set-function", test_weak_pointer_set_function); g_test_add_func ("/object/weak-ref", test_weak_ref); g_test_add_func ("/object/toggle-ref", test_toggle_ref); g_test_add_func ("/object/qdata", test_object_qdata);