Mon, 18 Apr 2022 17:26:21 +0200
#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 +}