Tue, 18 Apr 2023 19:10:45 +0200
add destructor functions for maps - fixes #253
src/cx/map.h | file | annotate | diff | comparison | revisions | |
src/hash_map.c | file | annotate | diff | comparison | revisions | |
tests/test_map.cpp | file | annotate | diff | comparison | revisions |
1.1 --- a/src/cx/map.h Tue Apr 18 18:01:41 2023 +0200 1.2 +++ b/src/cx/map.h Tue Apr 18 19:10:45 2023 +0200 1.3 @@ -102,7 +102,8 @@ 1.4 __attribute__((__nonnull__)) 1.5 void *(*remove)( 1.6 CxMap *map, 1.7 - CxHashKey key 1.8 + CxHashKey key, 1.9 + bool destroy 1.10 ); 1.11 1.12 /** 1.13 @@ -196,7 +197,6 @@ 1.14 */ 1.15 __attribute__((__nonnull__)) 1.16 static inline void cxMapDestroy(CxMap *map) { 1.17 - // TODO: likely to add auto-free feature for contents in the future 1.18 map->cl->destructor(map); 1.19 } 1.20 1.21 @@ -246,42 +246,72 @@ 1.22 /** 1.23 * Removes a key/value-pair from the map by using the key. 1.24 * 1.25 - * If this map is storing pointers, you should make sure that the map 1.26 - * is not the last location where this pointer is stored. 1.27 - * Otherwise, use cxMapRemoveAndGet() to retrieve the pointer while 1.28 - * removing it from the map. 1.29 + * Always invokes the destructor function, if any, on the removed element. 1.30 + * If this map is storing pointers and you just want to retrieve the pointer 1.31 + * without invoking the destructor, use cxMapRemoveAndGet(). 1.32 + * If you just want to detach the element from the map without invoking the 1.33 + * destructor or returning the element, use cxMapDetach(). 1.34 * 1.35 * @param map the map 1.36 * @param key the key 1.37 * @see cxMapRemoveAndGet() 1.38 + * @see cxMapDetach() 1.39 */ 1.40 __attribute__((__nonnull__)) 1.41 static inline void cxMapRemove( 1.42 CxMap *map, 1.43 CxHashKey key 1.44 ) { 1.45 - (void) map->cl->remove(map, key); 1.46 + (void) map->cl->remove(map, key, true); 1.47 +} 1.48 + 1.49 +/** 1.50 + * Detaches a key/value-pair from the map by using the key 1.51 + * without invoking the destructor. 1.52 + * 1.53 + * In general, you should only use this function if the map does not own 1.54 + * the data and there is a valid reference to the data somewhere else 1.55 + * in the program. In all other cases it is prefarable to use 1.56 + * cxMapRemove() or cxMapRemoveAndGet(). 1.57 + * 1.58 + * @param map the map 1.59 + * @param key the key 1.60 + * @see cxMapRemove() 1.61 + * @see cxMapRemoveAndGet() 1.62 + */ 1.63 +__attribute__((__nonnull__)) 1.64 +static inline void cxMapDetach( 1.65 + CxMap *map, 1.66 + CxHashKey key 1.67 +) { 1.68 + (void) map->cl->remove(map, key, false); 1.69 } 1.70 1.71 /** 1.72 * Removes a key/value-pair from the map by using the key. 1.73 * 1.74 - * This function should only be used when the map is storing pointers, 1.75 - * in order to retrieve the pointer you are about to remove. 1.76 - * In any other case, cxMapRemove() is sufficient. 1.77 + * This function can be used when the map is storing pointers, 1.78 + * in order to retrieve the pointer from the map without invoking 1.79 + * any destructor function. Sometimes you do not want the pointer 1.80 + * to be returned - in that case (instead of suppressing the "unused 1.81 + * result" warning) you can use cxMapDetach(). 1.82 + * 1.83 + * If this map is not storing pointers, this function behaves like 1.84 + * cxMapRemove() and returns \c NULL. 1.85 * 1.86 * @param map the map 1.87 * @param key the key 1.88 * @return the stored pointer or \c NULL if either the key is not present 1.89 * in the map or the map is not storing pointers 1.90 * @see cxMapStorePointers() 1.91 + * @see cxMapDetach() 1.92 */ 1.93 __attribute__((__nonnull__, __warn_unused_result__)) 1.94 static inline void *cxMapRemoveAndGet( 1.95 CxMap *map, 1.96 CxHashKey key 1.97 ) { 1.98 - return map->cl->remove(map, key); 1.99 + return map->cl->remove(map, key, !map->store_pointer); 1.100 } 1.101 1.102 // TODO: set-like map operations (union, intersect, difference)
2.1 --- a/src/hash_map.c Tue Apr 18 18:01:41 2023 +0200 2.2 +++ b/src/hash_map.c Tue Apr 18 19:10:45 2023 +0200 2.3 @@ -48,6 +48,8 @@ 2.4 if (elem != NULL) { 2.5 do { 2.6 struct cx_hash_map_element_s *next = elem->next; 2.7 + // invoke the destructor 2.8 + cx_invoke_destructor(map, elem->data); 2.9 // free the key data 2.10 cxFree(map->allocator, elem->key.data.obj); 2.11 // free the node 2.12 @@ -80,7 +82,7 @@ 2.13 void *value 2.14 ) { 2.15 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; 2.16 - CxAllocator *allocator = map->allocator; 2.17 + CxAllocator const *allocator = map->allocator; 2.18 2.19 unsigned hash = key.hash; 2.20 if (hash == 0) { 2.21 @@ -172,12 +174,14 @@ 2.22 * @param map the map 2.23 * @param key the key to look up 2.24 * @param remove flag indicating whether the looked up entry shall be removed 2.25 + * @param destroy flag indicating whether the destructor shall be invoked 2.26 * @return a pointer to the value corresponding to the key or \c NULL 2.27 */ 2.28 static void *cx_hash_map_get_remove( 2.29 CxMap *map, 2.30 CxHashKey key, 2.31 - bool remove 2.32 + bool remove, 2.33 + bool destroy 2.34 ) { 2.35 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; 2.36 2.37 @@ -194,10 +198,14 @@ 2.38 if (elm->key.hash == hash && elm->key.len == key.len) { 2.39 if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) { 2.40 void *data = NULL; 2.41 - if (map->store_pointer) { 2.42 - data = *(void **) elm->data; 2.43 - } else if (!remove) { 2.44 - data = elm->data; 2.45 + if (destroy) { 2.46 + cx_invoke_destructor(map, elm->data); 2.47 + } else { 2.48 + if (map->store_pointer) { 2.49 + data = *(void **) elm->data; 2.50 + } else { 2.51 + data = elm->data; 2.52 + } 2.53 } 2.54 if (remove) { 2.55 cx_hash_map_unlink(hash_map, slot, prev, elm); 2.56 @@ -217,14 +225,15 @@ 2.57 CxHashKey key 2.58 ) { 2.59 // we can safely cast, because we know when remove=false, the map stays untouched 2.60 - return cx_hash_map_get_remove((CxMap *) map, key, false); 2.61 + return cx_hash_map_get_remove((CxMap *) map, key, false, false); 2.62 } 2.63 2.64 static void *cx_hash_map_remove( 2.65 CxMap *map, 2.66 - CxHashKey key 2.67 + CxHashKey key, 2.68 + bool destroy 2.69 ) { 2.70 - return cx_hash_map_get_remove(map, key, true); 2.71 + return cx_hash_map_get_remove(map, key, true, destroy); 2.72 } 2.73 2.74 static void *cx_hash_map_iter_current_entry(void const *it) { 2.75 @@ -280,6 +289,9 @@ 2.76 } 2.77 } 2.78 2.79 + // destroy 2.80 + cx_invoke_destructor((struct cx_map_s *) map, elm->data); 2.81 + 2.82 // unlink 2.83 cx_hash_map_unlink(map, iter->slot, prev, elm); 2.84
3.1 --- a/tests/test_map.cpp Tue Apr 18 18:01:41 2023 +0200 3.2 +++ b/tests/test_map.cpp Tue Apr 18 19:10:45 2023 +0200 3.3 @@ -282,7 +282,7 @@ 3.4 TEST(CxHashMap, Clear) { 3.5 CxTestingAllocator allocator; 3.6 auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0); 3.7 - 3.8 + 3.9 cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1"); 3.10 cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2"); 3.11 cxMapPut(map, cx_hash_key_str("key 3"), (void *) "val 3"); 3.12 @@ -344,3 +344,100 @@ 3.13 EXPECT_TRUE(allocator.verify()); 3.14 } 3.15 3.16 +static void test_simple_destructor(void *data) { 3.17 + strcpy((char *) data, "OK"); 3.18 +} 3.19 + 3.20 +static void test_advanced_destructor( 3.21 + [[maybe_unused]] void *unused, 3.22 + void *data 3.23 +) { 3.24 + strcpy((char *) data, "OK"); 3.25 +} 3.26 + 3.27 +static void verify_any_destructor(CxMap *map) { 3.28 + auto k1 = cx_hash_key_str("key 1"); 3.29 + auto k2 = cx_hash_key_str("key 2"); 3.30 + auto k3 = cx_hash_key_str("key 3"); 3.31 + auto k4 = cx_hash_key_str("key 4"); 3.32 + auto k5 = cx_hash_key_str("key 5"); 3.33 + 3.34 + char v1[] = "val 1"; 3.35 + char v2[] = "val 2"; 3.36 + char v3[] = "val 3"; 3.37 + char v4[] = "val 4"; 3.38 + char v5[] = "val 5"; 3.39 + 3.40 + cxMapPut(map, k1, (void *) v1); 3.41 + cxMapPut(map, k2, (void *) v2); 3.42 + cxMapPut(map, k3, (void *) v3); 3.43 + cxMapPut(map, k4, (void *) v4); 3.44 + 3.45 + cxMapRemove(map, k2); 3.46 + auto r = cxMapRemoveAndGet(map, k3); 3.47 + cxMapDetach(map, k1); 3.48 + 3.49 + EXPECT_STREQ(v1, "val 1"); 3.50 + EXPECT_STREQ(v2, "OK"); 3.51 + EXPECT_STREQ(v3, "val 3"); 3.52 + EXPECT_STREQ(v4, "val 4"); 3.53 + EXPECT_STREQ(v5, "val 5"); 3.54 + EXPECT_EQ(r, v3); 3.55 + 3.56 + cxMapClear(map); 3.57 + 3.58 + EXPECT_STREQ(v1, "val 1"); 3.59 + EXPECT_STREQ(v2, "OK"); 3.60 + EXPECT_STREQ(v3, "val 3"); 3.61 + EXPECT_STREQ(v4, "OK"); 3.62 + EXPECT_STREQ(v5, "val 5"); 3.63 + 3.64 + cxMapPut(map, k1, (void *) v1); 3.65 + cxMapPut(map, k3, (void *) v3); 3.66 + cxMapPut(map, k5, (void *) v5); 3.67 + 3.68 + { 3.69 + auto iter = cxMapMutIteratorKeys(map); 3.70 + cx_foreach(CxHashKey*, key, iter) { 3.71 + if (key->data.cstr[4] == '1') cxIteratorFlagRemoval(iter); 3.72 + } 3.73 + } 3.74 + { 3.75 + auto iter = cxMapMutIteratorValues(map); 3.76 + cx_foreach(char*, v, iter) { 3.77 + if (v[4] == '5') cxIteratorFlagRemoval(iter); 3.78 + } 3.79 + } 3.80 + 3.81 + EXPECT_STREQ(v1, "OK"); 3.82 + EXPECT_STREQ(v2, "OK"); 3.83 + EXPECT_STREQ(v3, "val 3"); 3.84 + EXPECT_STREQ(v4, "OK"); 3.85 + EXPECT_STREQ(v5, "OK"); 3.86 + 3.87 + v1[0] = v2[0] = v4[0] = v5[0] = 'c'; 3.88 + 3.89 + cxMapDestroy(map); 3.90 + 3.91 + EXPECT_STREQ(v1, "cK"); 3.92 + EXPECT_STREQ(v2, "cK"); 3.93 + EXPECT_STREQ(v3, "OK"); 3.94 + EXPECT_STREQ(v4, "cK"); 3.95 + EXPECT_STREQ(v5, "cK"); 3.96 +} 3.97 + 3.98 +TEST(CxHashMap, SimpleDestructor) { 3.99 + CxTestingAllocator allocator; 3.100 + auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0); 3.101 + map->simple_destructor = test_simple_destructor; 3.102 + verify_any_destructor(map); 3.103 + EXPECT_TRUE(allocator.verify()); 3.104 +} 3.105 + 3.106 +TEST(CxHashMap, AdvancedDestructor) { 3.107 + CxTestingAllocator allocator; 3.108 + auto map = cxHashMapCreate(&allocator, CX_STORE_POINTERS, 0); 3.109 + map->advanced_destructor = test_advanced_destructor; 3.110 + verify_any_destructor(map); 3.111 + EXPECT_TRUE(allocator.verify()); 3.112 +}