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 |
--- 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()); +}