From d68e7bc84a1e9fe7413388760d9286ed59bcf06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Dec 2022 04:08:10 +0100 Subject: [PATCH] ghash: Add functions to steal all keys and values preserving ownership Add functions to steal all the keys or values from a ghash (especially useful when it's used as a set), passing the ownership of then to a GPtrArray container that preserves the destroy notify functions. --- docs/reference/glib/glib-sections.txt.in | 2 + glib/ghash.c | 74 ++++++++++++++++ glib/ghash.h | 4 + glib/tests/hash.c | 102 +++++++++++++++++++++++ 4 files changed, 182 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index 2ac3af6bb..ab7453e60 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -2578,6 +2578,8 @@ GHFunc g_hash_table_remove g_hash_table_steal g_hash_table_steal_extended +g_hash_table_steal_all_keys +g_hash_table_steal_all_values g_hash_table_foreach_remove g_hash_table_foreach_steal g_hash_table_remove_all diff --git a/glib/ghash.c b/glib/ghash.c index 6b6adc501..64edc0a34 100644 --- a/glib/ghash.c +++ b/glib/ghash.c @@ -1942,6 +1942,80 @@ g_hash_table_steal_all (GHashTable *hash_table) g_hash_table_maybe_resize (hash_table); } +/** + * g_hash_table_steal_all_keys: (skip) + * @hash_table: a #GHashTable + * + * Removes all keys and their associated values from a #GHashTable + * without calling the key destroy functions, returning the keys + * as a #GPtrArray with the free func set to the @hash_table key + * destroy function. + * + * Returns: (transfer container): a #GPtrArray containing each key of + * the table. Unref with with g_ptr_array_unref() when done. + * + * Since: 2.76 + */ +GPtrArray * +g_hash_table_steal_all_keys (GHashTable *hash_table) +{ + GPtrArray *array; + GDestroyNotify key_destroy_func; + + g_return_val_if_fail (hash_table != NULL, NULL); + + array = g_hash_table_get_keys_as_ptr_array (hash_table); + + /* Ignore the key destroy notify calls during removal, and use it for the + * array elements instead, but restore it after the hash table has been + * cleared, so that newly added keys will continue using it. + */ + key_destroy_func = g_steal_pointer (&hash_table->key_destroy_func); + g_ptr_array_set_free_func (array, key_destroy_func); + + g_hash_table_remove_all (hash_table); + hash_table->key_destroy_func = g_steal_pointer (&key_destroy_func); + + return array; +} + +/** + * g_hash_table_steal_all_values: (skip) + * @hash_table: a #GHashTable + * + * Removes all keys and their associated values from a #GHashTable + * without calling the value destroy functions, returning the values + * as a #GPtrArray with the free func set to the @hash_table value + * destroy function. + * + * Returns: (transfer container): a #GPtrArray containing each value of + * the table. Unref with with g_ptr_array_unref() when done. + * + * Since: 2.76 + */ +GPtrArray * +g_hash_table_steal_all_values (GHashTable *hash_table) +{ + GPtrArray *array; + GDestroyNotify value_destroy_func; + + g_return_val_if_fail (hash_table != NULL, NULL); + + array = g_hash_table_get_values_as_ptr_array (hash_table); + + /* Ignore the value destroy notify calls during removal, and use it for the + * array elements instead, but restore it after the hash table has been + * cleared, so that newly added values will continue using it. + */ + value_destroy_func = g_steal_pointer (&hash_table->value_destroy_func); + g_ptr_array_set_free_func (array, value_destroy_func); + + g_hash_table_remove_all (hash_table); + hash_table->value_destroy_func = g_steal_pointer (&value_destroy_func); + + return array; +} + /* * g_hash_table_foreach_remove_or_steal: * @hash_table: a #GHashTable diff --git a/glib/ghash.h b/glib/ghash.h index 604b07c8c..3eb8f3be0 100644 --- a/glib/ghash.h +++ b/glib/ghash.h @@ -94,6 +94,10 @@ gboolean g_hash_table_steal_extended (GHashTable *hash_table, gpointer *stolen_value); GLIB_AVAILABLE_IN_ALL void g_hash_table_steal_all (GHashTable *hash_table); +GLIB_AVAILABLE_IN_2_76 +GPtrArray * g_hash_table_steal_all_keys (GHashTable *hash_table); +GLIB_AVAILABLE_IN_2_76 +GPtrArray * g_hash_table_steal_all_values (GHashTable *hash_table); GLIB_AVAILABLE_IN_ALL gpointer g_hash_table_lookup (GHashTable *hash_table, gconstpointer key); diff --git a/glib/tests/hash.c b/glib/tests/hash.c index d929f531e..d08ddd57e 100644 --- a/glib/tests/hash.c +++ b/glib/tests/hash.c @@ -1749,6 +1749,106 @@ test_set_get_values_as_ptr_array (void) g_clear_pointer (&array, g_ptr_array_unref); } +static void +test_steal_all_keys (void) +{ + GHashTable *table; + GPtrArray *array; + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_insert (table, g_strdup ("xyz"), GUINT_TO_POINTER (0)); + g_hash_table_insert (table, g_strdup ("xyz"), GUINT_TO_POINTER (1)); + g_hash_table_insert (table, g_strdup ("abc"), GUINT_TO_POINTER (2)); + + array = g_hash_table_steal_all_keys (table); + g_assert_cmpuint (g_hash_table_size (table), ==, 0); + + g_hash_table_insert (table, g_strdup ("do-not-leak-me"), GUINT_TO_POINTER (5)); + g_clear_pointer (&table, g_hash_table_unref); + + g_assert_cmpint (array->len, ==, 2); + g_ptr_array_add (array, NULL); + + g_assert_true ( + g_strv_equal ((const gchar * const[]) { "xyz", "abc", NULL }, + (const gchar * const*) array->pdata) || + g_strv_equal ((const gchar * const[]) { "abc", "xyz", NULL }, + (const gchar * const*) array->pdata) + ); + + g_clear_pointer (&array, g_ptr_array_unref); + + table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + g_hash_table_insert (table, GUINT_TO_POINTER (0), g_strdup ("xyz")); + g_hash_table_insert (table, GUINT_TO_POINTER (1), g_strdup ("xyz")); + g_hash_table_insert (table, GUINT_TO_POINTER (2), g_strdup ("abc")); + + array = g_hash_table_steal_all_keys (table); + g_assert_cmpuint (g_hash_table_size (table), ==, 0); + + g_hash_table_insert (table, GUINT_TO_POINTER (5), g_strdup ("do-not-leak-me")); + g_clear_pointer (&table, g_hash_table_unref); + + g_assert_cmpint (array->len, ==, 3); + g_assert_true (g_ptr_array_find (array, GUINT_TO_POINTER (0), NULL)); + g_assert_true (g_ptr_array_find (array, GUINT_TO_POINTER (1), NULL)); + g_assert_true (g_ptr_array_find (array, GUINT_TO_POINTER (2), NULL)); + + g_clear_pointer (&array, g_ptr_array_unref); +} + +static void +test_steal_all_values (void) +{ + GHashTable *table; + GPtrArray *array; + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_insert (table, g_strdup ("xyz"), GUINT_TO_POINTER (0)); + g_hash_table_insert (table, g_strdup ("xyz"), GUINT_TO_POINTER (1)); + g_hash_table_insert (table, g_strdup ("abc"), GUINT_TO_POINTER (2)); + + array = g_hash_table_steal_all_values (table); + g_assert_cmpuint (g_hash_table_size (table), ==, 0); + + g_hash_table_insert (table, g_strdup ("do-not-leak-me"), GUINT_TO_POINTER (5)); + g_clear_pointer (&table, g_hash_table_unref); + + g_assert_cmpint (array->len, ==, 2); + g_assert_true (g_ptr_array_find (array, GUINT_TO_POINTER (1), NULL)); + g_assert_true (g_ptr_array_find (array, GUINT_TO_POINTER (2), NULL)); + + g_assert_true ( + memcmp ((gpointer []) { GUINT_TO_POINTER (1), GUINT_TO_POINTER (2) }, + array->pdata, array->len * sizeof (gpointer)) == 0 || + memcmp ((gpointer []) { GUINT_TO_POINTER (2), GUINT_TO_POINTER (1) }, + array->pdata, array->len * sizeof (gpointer)) == 0 + ); + + g_clear_pointer (&array, g_ptr_array_unref); + + table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + g_hash_table_insert (table, GUINT_TO_POINTER (0), g_strdup ("xyz")); + g_hash_table_insert (table, GUINT_TO_POINTER (1), g_strdup ("foo")); + g_hash_table_insert (table, GUINT_TO_POINTER (2), g_strdup ("abc")); + + array = g_hash_table_steal_all_values (table); + g_assert_cmpuint (g_hash_table_size (table), ==, 0); + + g_hash_table_insert (table, GUINT_TO_POINTER (5), g_strdup ("do-not-leak-me")); + g_clear_pointer (&table, g_hash_table_unref); + + g_assert_cmpint (array->len, ==, 3); + g_assert_true ( + g_ptr_array_find_with_equal_func (array, "xyz", g_str_equal, NULL)); + g_assert_true ( + g_ptr_array_find_with_equal_func (array, "foo", g_str_equal, NULL)); + g_assert_true ( + g_ptr_array_find_with_equal_func (array, "abc", g_str_equal, NULL)); + + g_clear_pointer (&array, g_ptr_array_unref); +} + static gboolean is_prime (guint p) { @@ -1821,6 +1921,8 @@ main (int argc, char *argv[]) g_test_add_func ("/hash/foreach-steal", test_foreach_steal); g_test_add_func ("/hash/steal-extended", test_steal_extended); g_test_add_func ("/hash/steal-extended/optional", test_steal_extended_optional); + g_test_add_func ("/hash/steal-all-keys", test_steal_all_keys); + g_test_add_func ("/hash/steal-all-values", test_steal_all_values); g_test_add_func ("/hash/lookup-extended", test_lookup_extended); g_test_add_func ("/hash/new-similar", test_new_similar);