utils: Add g_steal_handle_id() to complement g_clear_handle_id()

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 <pwithnall@gnome.org>
This commit is contained in:
Philip Withnall 2024-09-26 17:57:37 +01:00
parent cc2b78ec47
commit 431e75fa36
No known key found for this signature in database
GPG Key ID: DCDF5885B1F3ED73
2 changed files with 60 additions and 0 deletions

View File

@ -866,6 +866,51 @@ void g_clear_handle_id (guint *tag_ptr,
} G_STMT_END \ } G_STMT_END \
GLIB_AVAILABLE_MACRO_IN_2_56 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 isnt 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 */ /* Idles, child watchers and timeouts */
GLIB_AVAILABLE_IN_ALL GLIB_AVAILABLE_IN_ALL
guint g_timeout_add_full (gint priority, guint g_timeout_add_full (gint priority,

View File

@ -1322,6 +1322,20 @@ test_clear_slist (void)
g_assert_null (slist); 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 int
main (int argc, main (int argc,
char *argv[]) char *argv[])
@ -1382,6 +1396,7 @@ main (int argc,
g_test_add_func ("/utils/int-limits", test_int_limits); 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-list", test_clear_list);
g_test_add_func ("/utils/clear-slist", test_clear_slist); 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 (); return g_test_run ();
} }