garray: Add g_ptr_array_new_from_array() to copy a C array

It makes it easier (and more optimized) to create a GPtrArray from a C-style
array of pointers, in case using a GCopyFunc to duplicate the elements.
This commit is contained in:
Marco Trevisan (Treviño) 2022-12-13 18:33:52 +01:00
parent d5efa78a12
commit c5aedc88fc
4 changed files with 214 additions and 1 deletions

View File

@ -2717,6 +2717,7 @@ g_ptr_array_new_full
g_ptr_array_new_null_terminated
g_ptr_array_new_take
g_ptr_array_new_take_null_terminated
g_ptr_array_new_from_array
g_ptr_array_set_free_func
g_ptr_array_is_null_terminated
g_ptr_array_ref

View File

@ -1207,7 +1207,8 @@ g_ptr_array_new_take (gpointer *data,
* Creates a new #GPtrArray with @data as pointers, computing the length of it
* and setting the reference count to 1.
*
* This avoids having to copy such data manually.
* This avoids having to copy such data manually. @data will eventually be
* freed using g_free(), so must have been allocated with a suitable allocator.
*
* The length is calculated by iterating through @data until the first %NULL
* element is found.
@ -1243,6 +1244,69 @@ g_ptr_array_new_take_null_terminated (gpointer *data,
return array;
}
/**
* g_ptr_array_new_from_array: (skip)
* @data: (array length=len) (transfer none) (nullable): an array of pointers,
* or %NULL for an empty array
* @len: the number of pointers in @data
* @copy_func: (nullable): a copy function used to copy every element in the
* array or %NULL.
* @copy_func_user_data: user data passed to @copy_func, or %NULL
* @element_free_func: (nullable): a function to free elements on @array
* destruction or %NULL
*
* Creates a new #GPtrArray, copying @len pointers from @data, and setting
* the arrays reference count to 1.
*
* This avoids having to manually add each element one by one.
*
* If @copy_func is provided, then it is used to copy each element before
* adding them to the new array. If it is %NULL then the pointers are copied
* directly.
*
* It also sets @element_free_func for freeing each element when the array is
* destroyed either via g_ptr_array_unref(), when g_ptr_array_free() is called
* with @free_segment set to %TRUE or when removing elements.
*
* Do not use it if @len is greater than %G_MAXUINT. #GPtrArray
* stores the length of its data in #guint, which may be shorter than
* #gsize.
*
* Returns: (transfer full): A new #GPtrArray
*
* Since: 2.76
*/
GPtrArray *
g_ptr_array_new_from_array (gpointer *data,
gsize len,
GCopyFunc copy_func,
gpointer copy_func_user_data,
GDestroyNotify element_free_func)
{
GPtrArray *array;
GRealPtrArray *rarray;
g_return_val_if_fail (data != NULL || len == 0, NULL);
g_return_val_if_fail (len <= G_MAXUINT, NULL);
array = ptr_array_new (len, element_free_func, FALSE);
rarray = (GRealPtrArray *)array;
if (copy_func != NULL)
{
for (gsize i = 0; i < len; i++)
rarray->pdata[i] = copy_func (data[i], copy_func_user_data);
}
else
{
memcpy (rarray->pdata, data, len * sizeof (gpointer));
}
rarray->len = len;
return array;
}
/**
* g_ptr_array_steal:
* @array: a #GPtrArray.

View File

@ -146,6 +146,12 @@ GLIB_AVAILABLE_IN_2_76
GPtrArray* g_ptr_array_new_take (gpointer *data,
gsize len,
GDestroyNotify element_free_func);
GLIB_AVAILABLE_IN_2_76
GPtrArray* g_ptr_array_new_from_array (gpointer *data,
gsize len,
GCopyFunc copy_func,
gpointer copy_func_user_data,
GDestroyNotify element_free_func);
GLIB_AVAILABLE_IN_2_64
gpointer* g_ptr_array_steal (GPtrArray *array,
gsize *len);

View File

@ -1322,6 +1322,144 @@ pointer_array_new_take_null_terminated_from_gstrv (void)
g_free (joined);
}
static void
pointer_array_new_from_array (void)
{
const size_t array_size = 10000;
GPtrArray *source_array;
GPtrArray *gparray;
gpointer *old_pdata_copy;
source_array = g_ptr_array_new ();
for (size_t i = 0; i < array_size; i++)
g_ptr_array_add (source_array, GUINT_TO_POINTER (i));
g_assert_cmpuint (array_size, ==, source_array->len);
g_assert_nonnull (source_array->pdata);
gparray = g_ptr_array_new_from_array (source_array->pdata, source_array->len,
NULL, NULL, NULL);
old_pdata_copy =
g_memdup2 (source_array->pdata, source_array->len * sizeof (gpointer));
g_assert_nonnull (old_pdata_copy);
g_clear_pointer (&source_array, g_ptr_array_unref);
g_assert_false (g_ptr_array_is_null_terminated (gparray));
g_assert_cmpuint (gparray->len, ==, array_size);
g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 0)), ==, 0);
g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 10)), ==, 10);
g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer),
gparray->pdata, array_size * sizeof (gpointer));
g_ptr_array_add (gparray, GUINT_TO_POINTER (55));
g_ptr_array_insert (gparray, 0, GUINT_TO_POINTER (33));
g_assert_cmpuint (gparray->len, ==, array_size + 2);
g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 0)), ==, 33);
g_assert_cmpuint (
GPOINTER_TO_UINT (g_ptr_array_index (gparray, gparray->len - 1)), ==, 55);
g_ptr_array_remove_index (gparray, 0);
g_assert_cmpuint (gparray->len, ==, array_size + 1);
g_ptr_array_remove_index (gparray, gparray->len - 1);
g_assert_cmpuint (gparray->len, ==, array_size);
g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer),
gparray->pdata, array_size * sizeof (gpointer));
g_ptr_array_unref (gparray);
g_free (old_pdata_copy);
}
static void
pointer_array_new_from_array_empty (void)
{
GPtrArray *gparray;
gpointer empty_array[] = {0};
gparray = g_ptr_array_new_from_array (empty_array, 0, NULL, NULL, NULL);
g_assert_false (g_ptr_array_is_null_terminated (gparray));
g_assert_cmpuint (gparray->len, ==, 0);
g_clear_pointer (&gparray, g_ptr_array_unref);
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*data*!=*NULL*||*len*==*0*");
g_assert_null (g_ptr_array_new_from_array (NULL, 10, NULL, NULL, NULL));
g_test_assert_expected_messages ();
}
static void
pointer_array_new_from_array_overflow (void)
{
#if SIZE_WIDTH <= UINT_WIDTH
g_test_skip ("Overflow test requires UINT_WIDTH > SIZE_WIDTH.");
#else
if (!g_test_undefined ())
return;
/* Check for overflow should happen before data is accessed. */
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion 'len <= G_MAXUINT' failed");
g_assert_null (g_ptr_array_new_from_array (
(gpointer []) { NULL }, (gsize) G_MAXUINT + 1, NULL, NULL, NULL));
g_test_assert_expected_messages ();
#endif
}
static void
pointer_array_new_from_array_with_copy_and_free_func (void)
{
const size_t array_size = 10000;
GPtrArray *source_array;
GPtrArray *gparray;
gpointer *old_pdata_copy;
source_array = g_ptr_array_new_with_free_func (g_free);
for (size_t i = 0; i < array_size; i++)
g_ptr_array_add (source_array, g_strdup_printf ("%" G_GSIZE_FORMAT, i));
g_assert_cmpuint (array_size, ==, source_array->len);
g_assert_nonnull (source_array->pdata);
gparray = g_ptr_array_new_from_array (source_array->pdata, source_array->len,
(GCopyFunc) g_strdup, NULL, g_free);
old_pdata_copy =
g_memdup2 (source_array->pdata, source_array->len * sizeof (gpointer));
g_assert_nonnull (old_pdata_copy);
for (size_t i = 0; i < gparray->len; i++)
{
g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, i), ==,
(const char *) old_pdata_copy[i]);
}
g_clear_pointer (&source_array, g_ptr_array_unref);
g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 0), ==, "0");
g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 101), ==, "101");
g_ptr_array_add (gparray, g_strdup_printf ("%d", 55));
g_ptr_array_insert (gparray, 0, g_strdup_printf ("%d", 33));
g_assert_cmpuint (gparray->len, ==, array_size + 2);
g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 0), ==, "33");
g_assert_cmpstr (
(const char *) g_ptr_array_index (gparray, gparray->len - 1), ==, "55");
g_ptr_array_remove_index (gparray, 0);
g_assert_cmpuint (gparray->len, ==, array_size + 1);
g_ptr_array_remove_index (gparray, gparray->len - 1);
g_assert_cmpuint (gparray->len, ==, array_size);
g_ptr_array_unref (gparray);
g_free (old_pdata_copy);
}
static void
pointer_array_ref_count (gconstpointer test_data)
{
@ -2503,6 +2641,10 @@ main (int argc, char *argv[])
g_test_add_func ("/pointerarray/new-take-null-terminated/empty", pointer_array_new_take_null_terminated_empty);
g_test_add_func ("/pointerarray/new-take-null-terminated/with-free-func", pointer_array_new_take_null_terminated_with_free_func);
g_test_add_func ("/pointerarray/new-take-null-terminated/from-gstrv", pointer_array_new_take_null_terminated_from_gstrv);
g_test_add_func ("/pointerarray/new-from-array", pointer_array_new_from_array);
g_test_add_func ("/pointerarray/new-from-array/empty", pointer_array_new_from_array_empty);
g_test_add_func ("/pointerarray/new-from-array/overflow", pointer_array_new_from_array_overflow);
g_test_add_func ("/pointerarray/new-from-array/with-copy-and-free-func", pointer_array_new_from_array_with_copy_and_free_func);
g_test_add_data_func ("/pointerarray/ref-count/not-null-terminated", GINT_TO_POINTER (0), pointer_array_ref_count);
g_test_add_data_func ("/pointerarray/ref-count/null-terminated", GINT_TO_POINTER (1), pointer_array_ref_count);
g_test_add_func ("/pointerarray/free-func", pointer_array_free_func);