add destructor functions for maps - fixes #253

Tue, 18 Apr 2023 19:10:45 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 18 Apr 2023 19:10:45 +0200
changeset 686
64919f63f059
parent 685
2dd841e364af
child 687
612ed521b1c5

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 +}

mercurial