#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
--- a/src/cx/allocator.h	Mon Apr 18 16:56:29 2022 +0200
+++ b/src/cx/allocator.h	Mon Apr 18 17:26:21 2022 +0200
@@ -117,6 +117,57 @@
 typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__));
 
 /**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param data an optional pointer to custom data
+ * @param memory a pointer to the object to destruct
+  */
+typedef void (*cx_destructor_func2)(
+        void *data,
+        void *memory
+) __attribute__((__nonnull__(2)));
+
+/**
+ * Structure holding an advanced destructor function and the desired payload.
+ * Invocations of func should use data as first argument.
+ */
+typedef struct {
+    /**
+     * A pointer to the data that SHALL be used to invoke func.
+     */
+    void *data;
+    /**
+     * A pointer to the function to invoke.
+     */
+    cx_destructor_func2 func;
+} cx_advanced_destructor;
+
+/**
+ * Specifies the type of destructor to use.
+ */
+enum cx_destructor_type {
+    /**
+     * Do not use a destructor function.
+     */
+    CX_DESTRUCTOR_NONE,
+    /**
+     * Use a simple destructor.
+     * @see cx_destructor_func
+     */
+    CX_DESTRUCTOR_SIMPLE,
+    /**
+     * Use an advanced destructor.
+     * @see cx_advanced_destructor
+     */
+    CX_DESTRUCTOR_ADVANCED
+};
+
+/**
  * Allocate \p n bytes of memory.
  *
  * @param allocator the allocator
--- a/src/cx/linked_list.h	Mon Apr 18 16:56:29 2022 +0200
+++ b/src/cx/linked_list.h	Mon Apr 18 17:26:21 2022 +0200
@@ -48,7 +48,7 @@
 /**
  * Allocates a linked list for storing elements with \p item_size bytes each.
  *
- * @remark Elements added to the list are copied, therefore a possible cx_list_s.content_destructor
+ * @remark Elements added to the list are copied, therefore a possible destructor
  * MUST NOT free the memory pointed to by its argument.
  *
  * @param allocator the allocator for allocating the list nodes
@@ -67,7 +67,7 @@
  *
  * If you want to store the elements directly in this list, use cxLinkedListCreate().
  *
- * @remark Since only pointers are stored in this list, a possible cx_list_s.content_destructor
+ * @remark Since only pointers are stored in this list, a possible destructor
  * MAY free the memory pointed to by its argument in order to prevent memory leaks.
  *
  * @param allocator the allocator for allocating the list nodes
@@ -82,7 +82,7 @@
 /**
  * Creates a linked list using the data from an array.
  *
- * @remark Elements added to the list are copied, therefore a possible cx_list_s.content_destructor
+ * @remark Elements added to the list are copied, therefore a possible destructor
  * MUST NOT free the memory pointed to by its argument.
  *
  * @param allocator the allocator for allocating the list nodes
--- a/src/cx/list.h	Mon Apr 18 16:56:29 2022 +0200
+++ b/src/cx/list.h	Mon Apr 18 17:26:21 2022 +0200
@@ -71,13 +71,6 @@
      */
     CxAllocator const *allocator;
     /**
-     * An optional destructor for the list contents.
-     *
-     * @attention Read the documentation of the particular list implementation
-     * whether this destructor shall only destroy the contents or also free the memory.
-     */
-    cx_destructor_func content_destructor;
-    /**
      * The comparator function for the elements.
      */
     CxListComparator cmpfunc;
@@ -93,6 +86,30 @@
      * The capacity of the list (maximum number of elements).
      */
     size_t capacity;
+    union {
+        /**
+         * An optional simple destructor for the list contents that admits the free() interface.
+         *
+         * @remark Set content_destructor_type to #CX_DESTRUCTOR_SIMPLE.
+         *
+         * @attention Read the documentation of the particular list implementation
+         * whether this destructor shall only destroy the contents or also free the memory.
+         */
+        cx_destructor_func simple_destructor;
+        /**
+         * An optional advanced destructor for the list contents providing additional data.
+         *
+         * @remark Set content_destructor_type to #CX_DESTRUCTOR_ADVANCED.
+         *
+         * @attention Read the documentation of the particular list implementation
+         * whether this destructor shall only destroy the contents or also free the memory.
+         */
+        cx_advanced_destructor advanced_destructor;
+    };
+    /**
+     * The type of destructor to use.
+     */
+    enum cx_destructor_type content_destructor_type;
 };
 
 /**
@@ -389,19 +406,17 @@
 }
 
 /**
- * Calls the list's destructor function for every element.
- * If CxList.autofree_content is \c true, the elements are automatically free'd
- * unless the content destructor function did not already do that.
- * Similarly, if CxList.autofree is \c true, the list structure is free'd, unless
- * the list destructor function did not already do that.
+ * Deallocates the memory of the specified list structure.
+ *
+ * Also calls content a destructor function, depending on the configuration
+ * in CxList.content_destructor_type.
  *
  * This function itself is a destructor function for the CxList.
  *
- * @param list the list which contents shall be destroyed
- * @return \p list if the list structure has not been free'd during the process
+ * @param list the list which shall be destroyed
  */
 __attribute__((__nonnull__))
-CxList *cxListDestroy(CxList *list);
+void cxListDestroy(CxList *list);
 
 #ifdef __cplusplus
 } /* extern "C" */
--- a/src/linked_list.c	Mon Apr 18 16:56:29 2022 +0200
+++ b/src/linked_list.c	Mon Apr 18 17:26:21 2022 +0200
@@ -779,8 +779,7 @@
         size_t item_size
 ) {
     cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
-    if (list == NULL)
-        return NULL;
+    if (list == NULL) return NULL;
 
     list->base.cl = &cx_linked_list_class;
     list->base.allocator = allocator;
@@ -796,8 +795,7 @@
         CxListComparator comparator
 ) {
     cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
-    if (list == NULL)
-        return NULL;
+    if (list == NULL) return NULL;
 
     list->base.cl = &cx_pointer_linked_list_class;
     list->base.allocator = allocator;
--- a/src/list.c	Mon Apr 18 16:56:29 2022 +0200
+++ b/src/list.c	Mon Apr 18 17:26:21 2022 +0200
@@ -28,14 +28,26 @@
 
 #include "cx/list.h"
 
-CxList *cxListDestroy(CxList *list) {
-    if (list->content_destructor != NULL) {
-        CxIterator iter = cxListBegin(list);
-        cx_foreach(void*, elem, iter) {
-            list->content_destructor(elem);
+void cxListDestroy(CxList *list) {
+    switch (list->content_destructor_type) {
+        case CX_DESTRUCTOR_SIMPLE: {
+            CxIterator iter = cxListBegin(list);
+            cx_foreach(void*, elem, iter) {
+                list->simple_destructor(elem);
+            }
+            break;
         }
+        case CX_DESTRUCTOR_ADVANCED: {
+            CxIterator iter = cxListBegin(list);
+            cx_foreach(void*, elem, iter) {
+                list->advanced_destructor.func(list->advanced_destructor.data, elem);
+            }
+            break;
+        }
+        case CX_DESTRUCTOR_NONE:
+            break; // nothing
     }
+
     list->cl->destructor(list);
     cxFree(list->allocator, list);
-    return NULL;
 }
--- a/test/test_list.cpp	Mon Apr 18 16:56:29 2022 +0200
+++ b/test/test_list.cpp	Mon Apr 18 17:26:21 2022 +0200
@@ -92,7 +92,10 @@
 }
 
 template<typename InputIter>
-static node_test_data create_nodes_test_data(InputIter begin, InputIter end) {
+static node_test_data create_nodes_test_data(
+        InputIter begin,
+        InputIter end
+) {
     if (begin == end) return node_test_data{nullptr};
     node *first = new node;
     first->data = *begin;
@@ -592,13 +595,17 @@
     }
 
     void verifyCreate(CxList *list) const {
+        EXPECT_EQ(list->content_destructor_type, CX_DESTRUCTOR_NONE);
         EXPECT_EQ(list->size, 0);
         EXPECT_EQ(list->capacity, (size_t) -1);
         EXPECT_EQ(list->allocator, &testingAllocator);
         EXPECT_EQ(list->cmpfunc, cmp_int);
     }
 
-    void verifyAdd(CxList *list, bool write_through) {
+    void verifyAdd(
+            CxList *list,
+            bool write_through
+    ) {
         auto len = testdata_len;
         cx_for_n (i, len) EXPECT_EQ(cxListAdd(list, &testdata.data[i]), 0);
         EXPECT_EQ(list->size, len);
@@ -741,7 +748,10 @@
         }
     }
 
-    static void verifyCompare(CxList *left, CxList *right) {
+    static void verifyCompare(
+            CxList *left,
+            CxList *right
+    ) {
         EXPECT_EQ(cxListCompare(left, right), 0);
         int x = 42;
         cxListAdd(left, &x);
@@ -757,7 +767,7 @@
         ASSERT_EQ(left->size, right->size);
         EXPECT_LT(cxListCompare(left, right), 0);
         EXPECT_GT(cxListCompare(right, left), 0);
-        *(int*)cxListAt(left, 15) = 10;
+        *(int *) cxListAt(left, 15) = 10;
         EXPECT_EQ(cxListCompare(left, right), 0);
     }
 };
@@ -879,3 +889,35 @@
     verifyCompare(left, right);
 }
 
+TEST_F(PointerLinkedList, NoDestructor) {
+    void *item = cxMalloc(&testingAllocator, sizeof(int));
+    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
+    cxListAdd(list, item);
+    ASSERT_FALSE(testingAllocator.verify());
+    cxListDestroy(list);
+    EXPECT_FALSE(testingAllocator.verify());
+    cxFree(&testingAllocator, item);
+    EXPECT_TRUE(testingAllocator.verify());
+}
+
+TEST_F(PointerLinkedList, SimpleDestructor) {
+    int item = 0;
+    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
+    list->content_destructor_type = CX_DESTRUCTOR_SIMPLE;
+    list->simple_destructor = [](void *elem) { *(int *) elem = 42; };
+    cxListAdd(list, &item);
+    cxListDestroy(list);
+    EXPECT_EQ(item, 42);
+}
+
+TEST_F(PointerLinkedList, AdvancedDestructor) {
+    void *item = cxMalloc(&testingAllocator, sizeof(int));
+    auto list = cxPointerLinkedListCreate(cxDefaultAllocator, cmp_int);
+    list->content_destructor_type = CX_DESTRUCTOR_ADVANCED;
+    list->advanced_destructor.data = &testingAllocator;
+    list->advanced_destructor.func = (cx_destructor_func2) cxFree;
+    cxListAdd(list, item);
+    ASSERT_FALSE(testingAllocator.verify());
+    cxListDestroy(list);
+    EXPECT_TRUE(testingAllocator.verify());
+}

mercurial