From 431e75fa361e65d69ea671f373c6c3a1aaf414e3 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 26 Sep 2024 17:57:37 +0100 Subject: [PATCH] utils: Add g_steal_handle_id() to complement g_clear_handle_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just like we have `g_steal_pointer()` and `g_clear_pointer()`, it would be useful to have a ‘steal’ version of `g_clear_handle_id()`. Particularly in situations where a clear function can’t be represented as a `GClearHandleFunc`, such as `g_dbus_connection_signal_unsubscribe()` (there’s no way of passing the `GDBusConnection` to it) — or in situations where a handle ID isn’t being released, but is being passed from one struct to another or from a local scope to a struct or vice-versa. Includes unit tests. Signed-off-by: Philip Withnall --- glib/gmain.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ glib/tests/utils.c | 15 +++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/glib/gmain.h b/glib/gmain.h index d42b51db0..cbc147b47 100644 --- a/glib/gmain.h +++ b/glib/gmain.h @@ -866,6 +866,51 @@ void g_clear_handle_id (guint *tag_ptr, } G_STMT_END \ GLIB_AVAILABLE_MACRO_IN_2_56 +/** + * g_steal_handle_id: + * @handle_pointer: (inout) (not optional): a pointer to a handle ID + * + * Sets @handle_pointer to `0`, returning the value that was there before. + * + * Conceptually, this transfers the ownership of the handle ID from the + * referenced variable to the ‘caller’ of the macro (ie: ‘steals’ the + * handle ID). + * + * This can be very useful to make ownership transfer explicit, or to prevent + * a handle from being released multiple times. For example: + * + * ```c + * void + * maybe_unsubscribe_signal (ContextStruct *data) + * { + * if (some_complex_logic (data)) + * { + * g_dbus_connection_signal_unsubscribe (data->connection, + * g_steal_handle_id (&data->subscription_id)); + * // now data->subscription_id isn’t a dangling handle + * } + * } + * ``` + * + * While [func@GLib.clear_handle_id] can be used in many of the same situations + * as `g_steal_handle_id()`, this is one situation where it cannot be used, as + * there is no way to pass the `GDBusConnection` to a + * [type@GLib.ClearHandleFunc]. + * + * Since: 2.84 + */ +GLIB_AVAILABLE_STATIC_INLINE_IN_2_84 +static inline unsigned int +g_steal_handle_id (unsigned int *handle_pointer) +{ + unsigned int handle; + + handle = *handle_pointer; + *handle_pointer = 0; + + return handle; +} + /* Idles, child watchers and timeouts */ GLIB_AVAILABLE_IN_ALL guint g_timeout_add_full (gint priority, diff --git a/glib/tests/utils.c b/glib/tests/utils.c index e71d9a6ef..3f7c804cd 100644 --- a/glib/tests/utils.c +++ b/glib/tests/utils.c @@ -1322,6 +1322,20 @@ test_clear_slist (void) g_assert_null (slist); } +static void +test_steal_handle_id (void) +{ + unsigned int handle_id = 0; + + g_assert_cmpuint (g_steal_handle_id (&handle_id), ==, 0); + g_assert_cmpuint (handle_id, ==, 0); + + handle_id = 5; /* pretend this is a meaningful handle */ + + g_assert_cmpuint (g_steal_handle_id (&handle_id), ==, 5); + g_assert_cmpuint (handle_id, ==, 0); +} + int main (int argc, char *argv[]) @@ -1382,6 +1396,7 @@ main (int argc, g_test_add_func ("/utils/int-limits", test_int_limits); g_test_add_func ("/utils/clear-list", test_clear_list); g_test_add_func ("/utils/clear-slist", test_clear_slist); + g_test_add_func ("/utils/steal-handle-id", test_steal_handle_id); return g_test_run (); }