#179 improve API for list content destruction

Mon, 18 Apr 2022 17:26:21 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 18 Apr 2022 17:26:21 +0200
changeset 528
4fbfac557df8
parent 527
08539b8273fa
child 529
814d51173f20

#179 improve API for list content destruction

src/cx/allocator.h file | annotate | diff | comparison | revisions
src/cx/linked_list.h file | annotate | diff | comparison | revisions
src/cx/list.h file | annotate | diff | comparison | revisions
src/linked_list.c file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
test/test_list.cpp file | annotate | diff | comparison | revisions
     1.1 --- a/src/cx/allocator.h	Mon Apr 18 16:56:29 2022 +0200
     1.2 +++ b/src/cx/allocator.h	Mon Apr 18 17:26:21 2022 +0200
     1.3 @@ -117,6 +117,57 @@
     1.4  typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__));
     1.5  
     1.6  /**
     1.7 + * Function pointer type for destructor functions.
     1.8 + *
     1.9 + * A destructor function deallocates possible contents and MAY free the memory
    1.10 + * pointed to by \p memory. Read the documentation of the respective function
    1.11 + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
    1.12 + * particular implementation.
    1.13 + *
    1.14 + * @param data an optional pointer to custom data
    1.15 + * @param memory a pointer to the object to destruct
    1.16 +  */
    1.17 +typedef void (*cx_destructor_func2)(
    1.18 +        void *data,
    1.19 +        void *memory
    1.20 +) __attribute__((__nonnull__(2)));
    1.21 +
    1.22 +/**
    1.23 + * Structure holding an advanced destructor function and the desired payload.
    1.24 + * Invocations of func should use data as first argument.
    1.25 + */
    1.26 +typedef struct {
    1.27 +    /**
    1.28 +     * A pointer to the data that SHALL be used to invoke func.
    1.29 +     */
    1.30 +    void *data;
    1.31 +    /**
    1.32 +     * A pointer to the function to invoke.
    1.33 +     */
    1.34 +    cx_destructor_func2 func;
    1.35 +} cx_advanced_destructor;
    1.36 +
    1.37 +/**
    1.38 + * Specifies the type of destructor to use.
    1.39 + */
    1.40 +enum cx_destructor_type {
    1.41 +    /**
    1.42 +     * Do not use a destructor function.
    1.43 +     */
    1.44 +    CX_DESTRUCTOR_NONE,
    1.45 +    /**
    1.46 +     * Use a simple destructor.
    1.47 +     * @see cx_destructor_func
    1.48 +     */
    1.49 +    CX_DESTRUCTOR_SIMPLE,
    1.50 +    /**
    1.51 +     * Use an advanced destructor.
    1.52 +     * @see cx_advanced_destructor
    1.53 +     */
    1.54 +    CX_DESTRUCTOR_ADVANCED
    1.55 +};
    1.56 +
    1.57 +/**
    1.58   * Allocate \p n bytes of memory.
    1.59   *
    1.60   * @param allocator the allocator
     2.1 --- a/src/cx/linked_list.h	Mon Apr 18 16:56:29 2022 +0200
     2.2 +++ b/src/cx/linked_list.h	Mon Apr 18 17:26:21 2022 +0200
     2.3 @@ -48,7 +48,7 @@
     2.4  /**
     2.5   * Allocates a linked list for storing elements with \p item_size bytes each.
     2.6   *
     2.7 - * @remark Elements added to the list are copied, therefore a possible cx_list_s.content_destructor
     2.8 + * @remark Elements added to the list are copied, therefore a possible destructor
     2.9   * MUST NOT free the memory pointed to by its argument.
    2.10   *
    2.11   * @param allocator the allocator for allocating the list nodes
    2.12 @@ -67,7 +67,7 @@
    2.13   *
    2.14   * If you want to store the elements directly in this list, use cxLinkedListCreate().
    2.15   *
    2.16 - * @remark Since only pointers are stored in this list, a possible cx_list_s.content_destructor
    2.17 + * @remark Since only pointers are stored in this list, a possible destructor
    2.18   * MAY free the memory pointed to by its argument in order to prevent memory leaks.
    2.19   *
    2.20   * @param allocator the allocator for allocating the list nodes
    2.21 @@ -82,7 +82,7 @@
    2.22  /**
    2.23   * Creates a linked list using the data from an array.
    2.24   *
    2.25 - * @remark Elements added to the list are copied, therefore a possible cx_list_s.content_destructor
    2.26 + * @remark Elements added to the list are copied, therefore a possible destructor
    2.27   * MUST NOT free the memory pointed to by its argument.
    2.28   *
    2.29   * @param allocator the allocator for allocating the list nodes
     3.1 --- a/src/cx/list.h	Mon Apr 18 16:56:29 2022 +0200
     3.2 +++ b/src/cx/list.h	Mon Apr 18 17:26:21 2022 +0200
     3.3 @@ -71,13 +71,6 @@
     3.4       */
     3.5      CxAllocator const *allocator;
     3.6      /**
     3.7 -     * An optional destructor for the list contents.
     3.8 -     *
     3.9 -     * @attention Read the documentation of the particular list implementation
    3.10 -     * whether this destructor shall only destroy the contents or also free the memory.
    3.11 -     */
    3.12 -    cx_destructor_func content_destructor;
    3.13 -    /**
    3.14       * The comparator function for the elements.
    3.15       */
    3.16      CxListComparator cmpfunc;
    3.17 @@ -93,6 +86,30 @@
    3.18       * The capacity of the list (maximum number of elements).
    3.19       */
    3.20      size_t capacity;
    3.21 +    union {
    3.22 +        /**
    3.23 +         * An optional simple destructor for the list contents that admits the free() interface.
    3.24 +         *
    3.25 +         * @remark Set content_destructor_type to #CX_DESTRUCTOR_SIMPLE.
    3.26 +         *
    3.27 +         * @attention Read the documentation of the particular list implementation
    3.28 +         * whether this destructor shall only destroy the contents or also free the memory.
    3.29 +         */
    3.30 +        cx_destructor_func simple_destructor;
    3.31 +        /**
    3.32 +         * An optional advanced destructor for the list contents providing additional data.
    3.33 +         *
    3.34 +         * @remark Set content_destructor_type to #CX_DESTRUCTOR_ADVANCED.
    3.35 +         *
    3.36 +         * @attention Read the documentation of the particular list implementation
    3.37 +         * whether this destructor shall only destroy the contents or also free the memory.
    3.38 +         */
    3.39 +        cx_advanced_destructor advanced_destructor;
    3.40 +    };
    3.41 +    /**
    3.42 +     * The type of destructor to use.
    3.43 +     */
    3.44 +    enum cx_destructor_type content_destructor_type;
    3.45  };
    3.46  
    3.47  /**
    3.48 @@ -389,19 +406,17 @@
    3.49  }
    3.50  
    3.51  /**
    3.52 - * Calls the list's destructor function for every element.
    3.53 - * If CxList.autofree_content is \c true, the elements are automatically free'd
    3.54 - * unless the content destructor function did not already do that.
    3.55 - * Similarly, if CxList.autofree is \c true, the list structure is free'd, unless
    3.56 - * the list destructor function did not already do that.
    3.57 + * Deallocates the memory of the specified list structure.
    3.58 + *
    3.59 + * Also calls content a destructor function, depending on the configuration
    3.60 + * in CxList.content_destructor_type.
    3.61   *
    3.62   * This function itself is a destructor function for the CxList.
    3.63   *
    3.64 - * @param list the list which contents shall be destroyed
    3.65 - * @return \p list if the list structure has not been free'd during the process
    3.66 + * @param list the list which shall be destroyed
    3.67   */
    3.68  __attribute__((__nonnull__))
    3.69 -CxList *cxListDestroy(CxList *list);
    3.70 +void cxListDestroy(CxList *list);
    3.71  
    3.72  #ifdef __cplusplus
    3.73  } /* extern "C" */
     4.1 --- a/src/linked_list.c	Mon Apr 18 16:56:29 2022 +0200
     4.2 +++ b/src/linked_list.c	Mon Apr 18 17:26:21 2022 +0200
     4.3 @@ -779,8 +779,7 @@
     4.4          size_t item_size
     4.5  ) {
     4.6      cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
     4.7 -    if (list == NULL)
     4.8 -        return NULL;
     4.9 +    if (list == NULL) return NULL;
    4.10  
    4.11      list->base.cl = &cx_linked_list_class;
    4.12      list->base.allocator = allocator;
    4.13 @@ -796,8 +795,7 @@
    4.14          CxListComparator comparator
    4.15  ) {
    4.16      cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
    4.17 -    if (list == NULL)
    4.18 -        return NULL;
    4.19 +    if (list == NULL) return NULL;
    4.20  
    4.21      list->base.cl = &cx_pointer_linked_list_class;
    4.22      list->base.allocator = allocator;
     5.1 --- a/src/list.c	Mon Apr 18 16:56:29 2022 +0200
     5.2 +++ b/src/list.c	Mon Apr 18 17:26:21 2022 +0200
     5.3 @@ -28,14 +28,26 @@
     5.4  
     5.5  #include "cx/list.h"
     5.6  
     5.7 -CxList *cxListDestroy(CxList *list) {
     5.8 -    if (list->content_destructor != NULL) {
     5.9 -        CxIterator iter = cxListBegin(list);
    5.10 -        cx_foreach(void*, elem, iter) {
    5.11 -            list->content_destructor(elem);
    5.12 +void cxListDestroy(CxList *list) {
    5.13 +    switch (list->content_destructor_type) {
    5.14 +        case CX_DESTRUCTOR_SIMPLE: {
    5.15 +            CxIterator iter = cxListBegin(list);
    5.16 +            cx_foreach(void*, elem, iter) {
    5.17 +                list->simple_destructor(elem);
    5.18 +            }
    5.19 +            break;
    5.20          }
    5.21 +        case CX_DESTRUCTOR_ADVANCED: {
    5.22 +            CxIterator iter = cxListBegin(list);
    5.23 +            cx_foreach(void*, elem, iter) {
    5.24 +                list->advanced_destructor.func(list->advanced_destructor.data, elem);
    5.25 +            }
    5.26 +            break;
    5.27 +        }
    5.28 +        case CX_DESTRUCTOR_NONE:
    5.29 +            break; // nothing
    5.30      }
    5.31 +
    5.32      list->cl->destructor(list);
    5.33      cxFree(list->allocator, list);
    5.34 -    return NULL;
    5.35  }
     6.1 --- a/test/test_list.cpp	Mon Apr 18 16:56:29 2022 +0200
     6.2 +++ b/test/test_list.cpp	Mon Apr 18 17:26:21 2022 +0200
     6.3 @@ -92,7 +92,10 @@
     6.4  }
     6.5  
     6.6  template<typename InputIter>
     6.7 -static node_test_data create_nodes_test_data(InputIter begin, InputIter end) {
     6.8 +static node_test_data create_nodes_test_data(
     6.9 +        InputIter begin,
    6.10 +        InputIter end
    6.11 +) {
    6.12      if (begin == end) return node_test_data{nullptr};
    6.13      node *first = new node;
    6.14      first->data = *begin;
    6.15 @@ -592,13 +595,17 @@
    6.16      }
    6.17  
    6.18      void verifyCreate(CxList *list) const {
    6.19 +        EXPECT_EQ(list->content_destructor_type, CX_DESTRUCTOR_NONE);
    6.20          EXPECT_EQ(list->size, 0);
    6.21          EXPECT_EQ(list->capacity, (size_t) -1);
    6.22          EXPECT_EQ(list->allocator, &testingAllocator);
    6.23          EXPECT_EQ(list->cmpfunc, cmp_int);
    6.24      }
    6.25  
    6.26 -    void verifyAdd(CxList *list, bool write_through) {
    6.27 +    void verifyAdd(
    6.28 +            CxList *list,
    6.29 +            bool write_through
    6.30 +    ) {
    6.31          auto len = testdata_len;
    6.32          cx_for_n (i, len) EXPECT_EQ(cxListAdd(list, &testdata.data[i]), 0);
    6.33          EXPECT_EQ(list->size, len);
    6.34 @@ -741,7 +748,10 @@
    6.35          }
    6.36      }
    6.37  
    6.38 -    static void verifyCompare(CxList *left, CxList *right) {
    6.39 +    static void verifyCompare(
    6.40 +            CxList *left,
    6.41 +            CxList *right
    6.42 +    ) {
    6.43          EXPECT_EQ(cxListCompare(left, right), 0);
    6.44          int x = 42;
    6.45          cxListAdd(left, &x);
    6.46 @@ -757,7 +767,7 @@
    6.47          ASSERT_EQ(left->size, right->size);
    6.48          EXPECT_LT(cxListCompare(left, right), 0);
    6.49          EXPECT_GT(cxListCompare(right, left), 0);
    6.50 -        *(int*)cxListAt(left, 15) = 10;
    6.51 +        *(int *) cxListAt(left, 15) = 10;
    6.52          EXPECT_EQ(cxListCompare(left, right), 0);
    6.53      }
    6.54  };
    6.55 @@ -879,3 +889,35 @@
    6.56      verifyCompare(left, right);
    6.57  }
    6.58  
    6.59 +TEST_F(PointerLinkedList, NoDestructor) {
    6.60 +    void *item = cxMalloc(&testingAllocator, sizeof(int));
    6.61 +    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
    6.62 +    cxListAdd(list, item);
    6.63 +    ASSERT_FALSE(testingAllocator.verify());
    6.64 +    cxListDestroy(list);
    6.65 +    EXPECT_FALSE(testingAllocator.verify());
    6.66 +    cxFree(&testingAllocator, item);
    6.67 +    EXPECT_TRUE(testingAllocator.verify());
    6.68 +}
    6.69 +
    6.70 +TEST_F(PointerLinkedList, SimpleDestructor) {
    6.71 +    int item = 0;
    6.72 +    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
    6.73 +    list->content_destructor_type = CX_DESTRUCTOR_SIMPLE;
    6.74 +    list->simple_destructor = [](void *elem) { *(int *) elem = 42; };
    6.75 +    cxListAdd(list, &item);
    6.76 +    cxListDestroy(list);
    6.77 +    EXPECT_EQ(item, 42);
    6.78 +}
    6.79 +
    6.80 +TEST_F(PointerLinkedList, AdvancedDestructor) {
    6.81 +    void *item = cxMalloc(&testingAllocator, sizeof(int));
    6.82 +    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
    6.83 +    list->content_destructor_type = CX_DESTRUCTOR_ADVANCED;
    6.84 +    list->advanced_destructor.data = &testingAllocator;
    6.85 +    list->advanced_destructor.func = (cx_destructor_func2) cxFree;
    6.86 +    cxListAdd(list, item);
    6.87 +    ASSERT_FALSE(testingAllocator.verify());
    6.88 +    cxListDestroy(list);
    6.89 +    EXPECT_TRUE(testingAllocator.verify());
    6.90 +}

mercurial