make hashmap store objects instead of pointers by default - fixes #239

Thu, 23 Feb 2023 18:58:15 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 23 Feb 2023 18:58:15 +0100
changeset 658
56c62780582e
parent 657
3eeadf666d6b
child 659
4a06fd63909a

make hashmap store objects instead of pointers by default - fixes #239

src/cx/hash_map.h file | annotate | diff | comparison | revisions
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/hash_map.h	Mon Feb 20 19:55:42 2023 +0100
     1.2 +++ b/src/cx/hash_map.h	Thu Feb 23 18:58:15 2023 +0100
     1.3 @@ -44,16 +44,7 @@
     1.4  #endif
     1.5  
     1.6  /** Internal structure for an element of a hash map. */
     1.7 -struct cx_hash_map_element_s {
     1.8 -    /** The value data. */
     1.9 -    void *data;
    1.10 -
    1.11 -    /** A pointer to the next element in the current bucket. */
    1.12 -    struct cx_hash_map_element_s *next;
    1.13 -
    1.14 -    /** The corresponding key. */
    1.15 -    CxHashKey key;
    1.16 -};
    1.17 +struct cx_hash_map_element_s;
    1.18  
    1.19  /**
    1.20   * Internal structure for a hash map.
    1.21 @@ -84,16 +75,39 @@
    1.22   * In other words, when the iterator is finished, \c index==size .
    1.23   *
    1.24   * @param allocator the allocator to use
    1.25 + * @param itemsize the size of one element
    1.26   * @param buckets the initial number of buckets in this hash map
    1.27   * @return a pointer to the new hash map
    1.28   */
    1.29  __attribute__((__nonnull__, __warn_unused_result__))
    1.30  CxMap *cxHashMapCreate(
    1.31          CxAllocator *allocator,
    1.32 +        size_t itemsize,
    1.33          size_t buckets
    1.34  );
    1.35  
    1.36  /**
    1.37 + * Convenience function for creating a hash map that is storing pointers.
    1.38 + *
    1.39 + * If \p buckets is zero, an implementation defined default will be used.
    1.40 + *
    1.41 + * @param allocator the allocator to use
    1.42 + * @param buckets the initial number of buckets in this hash map
    1.43 + * @return a pointer to the new hash map
    1.44 + */
    1.45 +__attribute__((__nonnull__, __warn_unused_result__))
    1.46 +static inline CxMap *cxHashMapCreateForPointers(
    1.47 +        CxAllocator *allocator,
    1.48 +        size_t buckets
    1.49 +) {
    1.50 +    CxMap *map = cxHashMapCreate(allocator, sizeof(void *), buckets);
    1.51 +    if (map != NULL) {
    1.52 +        map->store_pointers = true;
    1.53 +    }
    1.54 +    return map;
    1.55 +}
    1.56 +
    1.57 +/**
    1.58   * Increases the number of buckets, if necessary.
    1.59   *
    1.60   * The load threshold is \c 0.75*buckets. If the element count exceeds the load
     2.1 --- a/src/cx/map.h	Mon Feb 20 19:55:42 2023 +0100
     2.2 +++ b/src/cx/map.h	Thu Feb 23 18:58:15 2023 +0100
     2.3 @@ -63,7 +63,15 @@
     2.4      CxAllocator *allocator;
     2.5      /** The number of elements currently stored. */
     2.6      size_t size;
     2.7 -    // TODO: elemsize + a flag if values shall be copied to the map
     2.8 +    /**
     2.9 +     * The size of an element.
    2.10 +     */
    2.11 +    size_t itemsize;
    2.12 +    /**
    2.13 +     * True, if this map shall store pointers instead
    2.14 +     * of copies of objects.
    2.15 +     */
    2.16 +    bool store_pointers;
    2.17  };
    2.18  
    2.19  /**
    2.20 @@ -161,6 +169,38 @@
    2.21      void *value;
    2.22  };
    2.23  
    2.24 +/**
    2.25 + * Advises the map to store copies of the objects (default mode of operation).
    2.26 + *
    2.27 + * Retrieving objects from this map will yield pointers to the copies stored
    2.28 + * within this list.
    2.29 + *
    2.30 + * @param map the map
    2.31 + * @see cxMapStorePointers()
    2.32 + */
    2.33 +__attribute__((__nonnull__))
    2.34 +static inline void cxMapStoreObjects(CxMap *map) {
    2.35 +    map->store_pointers = false;
    2.36 +}
    2.37 +
    2.38 +/**
    2.39 + * Advises the map to only store pointers to the objects.
    2.40 + *
    2.41 + * Retrieving objects from this list will yield the original pointers stored.
    2.42 + *
    2.43 + * @note This function forcibly sets the element size to the size of a pointer.
    2.44 + * Invoking this function on a non-empty map that already stores copies of
    2.45 + * objects is undefined.
    2.46 + *
    2.47 + * @param map the map
    2.48 + * @see cxMapStoreObjects()
    2.49 + */
    2.50 +__attribute__((__nonnull__))
    2.51 +static inline void cxMapStorePointers(CxMap *map) {
    2.52 +    map->store_pointers = true;
    2.53 +    map->itemsize = sizeof(void *);
    2.54 +}
    2.55 +
    2.56  
    2.57  /**
    2.58   * Deallocates the memory of the specified map.
    2.59 @@ -221,7 +261,8 @@
    2.60   *
    2.61   * @param map the map
    2.62   * @param key the key
    2.63 - * @return the removed value
    2.64 + * @return if this map is storing pointers, the removed value, \c NULL otherwise
    2.65 + * @see cxMapStorePointers()
    2.66   */
    2.67  __attribute__((__nonnull__, __warn_unused_result__))
    2.68  static inline void *cxMapRemove(
     3.1 --- a/src/hash_map.c	Mon Feb 20 19:55:42 2023 +0100
     3.2 +++ b/src/hash_map.c	Thu Feb 23 18:58:15 2023 +0100
     3.3 @@ -30,6 +30,17 @@
     3.4  #include "cx/hash_map.h"
     3.5  #include "cx/utils.h"
     3.6  
     3.7 +struct cx_hash_map_element_s {
     3.8 +    /** A pointer to the next element in the current bucket. */
     3.9 +    struct cx_hash_map_element_s *next;
    3.10 +
    3.11 +    /** The corresponding key. */
    3.12 +    CxHashKey key;
    3.13 +
    3.14 +    /** The value data. */
    3.15 +    char data[];
    3.16 +};
    3.17 +
    3.18  static void cx_hash_map_clear(struct cx_map_s *map) {
    3.19      struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
    3.20      cx_for_n(i, hash_map->bucket_count) {
    3.21 @@ -89,17 +100,27 @@
    3.22      if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
    3.23          memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
    3.24          // overwrite existing element
    3.25 -        elm->data = value;
    3.26 +        if (map->store_pointers) {
    3.27 +            memcpy(elm->data, &value, sizeof(void *));
    3.28 +        } else {
    3.29 +            memcpy(elm->data, value, map->itemsize);
    3.30 +        }
    3.31      } else {
    3.32          // allocate new element
    3.33 -        struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s));
    3.34 +        struct cx_hash_map_element_s *e = cxMalloc(
    3.35 +                allocator,
    3.36 +                sizeof(struct cx_hash_map_element_s) + map->itemsize
    3.37 +        );
    3.38          if (e == NULL) {
    3.39              return -1;
    3.40          }
    3.41  
    3.42          // write the value
    3.43 -        // TODO: depending on future map features, we may want to copy here
    3.44 -        e->data = value;
    3.45 +        if (map->store_pointers) {
    3.46 +            memcpy(e->data, &value, sizeof(void *));
    3.47 +        } else {
    3.48 +            memcpy(e->data, value, map->itemsize);
    3.49 +        }
    3.50  
    3.51          // copy the key
    3.52          void *kd = cxMalloc(allocator, key.len);
    3.53 @@ -151,7 +172,7 @@
    3.54   * @param map the map
    3.55   * @param key the key to look up
    3.56   * @param remove flag indicating whether the looked up entry shall be removed
    3.57 - * @return the value corresponding to the key or \c NULL
    3.58 + * @return a pointer to the value corresponding to the key or \c NULL
    3.59   */
    3.60  static void *cx_hash_map_get_remove(
    3.61          CxMap *map,
    3.62 @@ -172,7 +193,12 @@
    3.63      while (elm && elm->key.hash <= hash) {
    3.64          if (elm->key.hash == hash && elm->key.len == key.len) {
    3.65              if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
    3.66 -                void *data = elm->data;
    3.67 +                void *data = NULL;
    3.68 +                if (map->store_pointers) {
    3.69 +                    data = *(void **) elm->data;
    3.70 +                } else if (!remove) {
    3.71 +                    data = elm->data;
    3.72 +                }
    3.73                  if (remove) {
    3.74                      cx_hash_map_unlink(hash_map, slot, prev, elm);
    3.75                  }
    3.76 @@ -215,9 +241,13 @@
    3.77  
    3.78  static void *cx_hash_map_iter_current_value(void const *it) {
    3.79      struct cx_iterator_s const *iter = it;
    3.80 +    struct cx_hash_map_s const *map = iter->src_handle;
    3.81      struct cx_hash_map_element_s *elm = iter->elem_handle;
    3.82 -    // TODO: return a pointer to data if this map is storing copies
    3.83 -    return elm->data;
    3.84 +    if (map->base.store_pointers) {
    3.85 +        return *(void **) elm->data;
    3.86 +    } else {
    3.87 +        return elm->data;
    3.88 +    }
    3.89  }
    3.90  
    3.91  static bool cx_hash_map_iter_valid(void const *it) {
    3.92 @@ -274,8 +304,11 @@
    3.93          iter->kv_data.value = NULL;
    3.94      } else {
    3.95          iter->kv_data.key = &elm->key;
    3.96 -        // TODO: pointer to data if this map is storing copies
    3.97 -        iter->kv_data.value = elm->data;
    3.98 +        if (map->base.store_pointers) {
    3.99 +            iter->kv_data.value = *(void **) elm->data;
   3.100 +        } else {
   3.101 +            iter->kv_data.value = elm->data;
   3.102 +        }
   3.103      }
   3.104  }
   3.105  
   3.106 @@ -311,8 +344,11 @@
   3.107          }
   3.108          iter.elem_handle = elm;
   3.109          iter.kv_data.key = &elm->key;
   3.110 -        // TODO: pointer to data if this map is storing copies
   3.111 -        iter.kv_data.value = elm->data;
   3.112 +        if (map->store_pointers) {
   3.113 +            iter.kv_data.value = *(void **) elm->data;
   3.114 +        } else {
   3.115 +            iter.kv_data.value = elm->data;
   3.116 +        }
   3.117      } else {
   3.118          iter.elem_handle = NULL;
   3.119          iter.kv_data.key = NULL;
   3.120 @@ -372,6 +408,7 @@
   3.121  
   3.122  CxMap *cxHashMapCreate(
   3.123          CxAllocator *allocator,
   3.124 +        size_t itemsize,
   3.125          size_t buckets
   3.126  ) {
   3.127      if (buckets == 0) {
   3.128 @@ -391,9 +428,11 @@
   3.129      }
   3.130  
   3.131      // initialize base members
   3.132 +    map->base.store_pointers = false;
   3.133      map->base.cl = &cx_hash_map_class;
   3.134      map->base.allocator = allocator;
   3.135      map->base.size = 0;
   3.136 +    map->base.itemsize = itemsize;
   3.137  
   3.138      return (CxMap *) map;
   3.139  }
     4.1 --- a/tests/test_map.cpp	Mon Feb 20 19:55:42 2023 +0100
     4.2 +++ b/tests/test_map.cpp	Thu Feb 23 18:58:15 2023 +0100
     4.3 @@ -28,6 +28,7 @@
     4.4  
     4.5  #include "cx/hash_map.h"
     4.6  #include "cx/utils.h"
     4.7 +#include "cx/string.h"
     4.8  #include "util_allocator.h"
     4.9  
    4.10  #include <gtest/gtest.h>
    4.11 @@ -114,14 +115,21 @@
    4.12  
    4.13  TEST(CxHashMap, Create) {
    4.14      CxTestingAllocator allocator;
    4.15 -    auto map = cxHashMapCreate(&allocator, 0);
    4.16 +    auto map = cxHashMapCreate(&allocator, 1, 0);
    4.17      auto hmap = reinterpret_cast<struct cx_hash_map_s *>(map);
    4.18      EXPECT_GT(hmap->bucket_count, 0);
    4.19      cx_for_n(i, hmap->bucket_count) {
    4.20          EXPECT_EQ(hmap->buckets[i], nullptr);
    4.21      }
    4.22 +    EXPECT_EQ(map->itemsize, 1);
    4.23      EXPECT_EQ(map->size, 0);
    4.24      EXPECT_EQ(map->allocator, &allocator);
    4.25 +    EXPECT_FALSE(map->store_pointers);
    4.26 +    cxMapStorePointers(map);
    4.27 +    EXPECT_TRUE(map->store_pointers);
    4.28 +    EXPECT_EQ(map->itemsize, sizeof(void *));
    4.29 +    cxMapStoreObjects(map);
    4.30 +    EXPECT_FALSE(map->store_pointers);
    4.31  
    4.32      cxMapDestroy(map);
    4.33      EXPECT_TRUE(allocator.verify());
    4.34 @@ -130,7 +138,7 @@
    4.35  TEST(CxHashMap, BasicOperations) {
    4.36      // create the map
    4.37      CxTestingAllocator allocator;
    4.38 -    auto map = cxHashMapCreate(&allocator, 8);
    4.39 +    auto map = cxHashMapCreateForPointers(&allocator, 8);
    4.40  
    4.41      // create a reference map
    4.42      std::unordered_map<std::string, std::string> refmap;
    4.43 @@ -174,7 +182,7 @@
    4.44  
    4.45  TEST(CxHashMap, RemoveViaIterator) {
    4.46      CxTestingAllocator allocator;
    4.47 -    auto map = cxHashMapCreate(&allocator, 4);
    4.48 +    auto map = cxHashMapCreateForPointers(&allocator, 4);
    4.49  
    4.50      cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
    4.51      cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
    4.52 @@ -203,7 +211,7 @@
    4.53  
    4.54  TEST(CxHashMap, RehashNotRequired) {
    4.55      CxTestingAllocator allocator;
    4.56 -    auto map = cxHashMapCreate(&allocator, 8);
    4.57 +    auto map = cxHashMapCreateForPointers(&allocator, 8);
    4.58  
    4.59      cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
    4.60      cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
    4.61 @@ -223,7 +231,7 @@
    4.62  
    4.63  TEST(CxHashMap, Rehash) {
    4.64      CxTestingAllocator allocator;
    4.65 -    auto map = cxHashMapCreate(&allocator, 8);
    4.66 +    auto map = cxHashMapCreateForPointers(&allocator, 8);
    4.67  
    4.68      cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
    4.69      cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
    4.70 @@ -252,7 +260,7 @@
    4.71  
    4.72  TEST(CxHashMap, Clear) {
    4.73      CxTestingAllocator allocator;
    4.74 -    auto map = cxHashMapCreate(&allocator, 0);
    4.75 +    auto map = cxHashMapCreateForPointers(&allocator, 0);
    4.76      
    4.77      cxMapPut(map, cx_hash_key_str("key 1"), (void *) "val 1");
    4.78      cxMapPut(map, cx_hash_key_str("key 2"), (void *) "val 2");
    4.79 @@ -269,4 +277,49 @@
    4.80  
    4.81      cxMapDestroy(map);
    4.82      EXPECT_TRUE(allocator.verify());
    4.83 -}
    4.84 \ No newline at end of file
    4.85 +}
    4.86 +
    4.87 +TEST(CxHashMap, StoreUcxStrings) {
    4.88 +    // create the map
    4.89 +    CxTestingAllocator allocator;
    4.90 +    auto map = cxHashMapCreate(&allocator, sizeof(cxstring), 8);
    4.91 +
    4.92 +    // define some strings
    4.93 +    cxstring s1 = CX_STR("this");
    4.94 +    cxstring s2 = CX_STR("is");
    4.95 +    cxstring s3 = CX_STR("a");
    4.96 +    cxstring s4 = CX_STR("test");
    4.97 +    cxstring s5 = CX_STR("setup");
    4.98 +
    4.99 +    // put them into the map
   4.100 +    cxMapPut(map, cx_hash_key_str("s1"), &s1);
   4.101 +    cxMapPut(map, cx_hash_key_str("s2"), &s2);
   4.102 +    cxMapPut(map, cx_hash_key_str("s3"), &s3);
   4.103 +    cxMapPut(map, cx_hash_key_str("s4"), &s4);
   4.104 +
   4.105 +    // overwrite a value
   4.106 +    cxMapPut(map, cx_hash_key_str("s1"), &s5);
   4.107 +
   4.108 +    // look up a string
   4.109 +    auto s3p = reinterpret_cast<cxstring *>(cxMapGet(map, cx_hash_key_str("s3")));
   4.110 +    EXPECT_EQ(s3p->length, s3.length);
   4.111 +    EXPECT_EQ(s3p->ptr, s3.ptr);
   4.112 +    EXPECT_NE(s3p, &s3);
   4.113 +
   4.114 +    // remove a string
   4.115 +    auto r = cxMapRemove(map, cx_hash_key_str("s2"));
   4.116 +    EXPECT_EQ(r, nullptr);
   4.117 +
   4.118 +    // iterate
   4.119 +    auto ref = std::vector{s5.ptr, s3.ptr, s4.ptr};
   4.120 +    auto iter = cxMapIteratorValues(map);
   4.121 +    cx_foreach(cxstring*, s, iter) {
   4.122 +        auto found = std::find(ref.begin(), ref.end(), s->ptr);
   4.123 +        ASSERT_NE(found, ref.end());
   4.124 +        ref.erase(found);
   4.125 +    }
   4.126 +    EXPECT_EQ(ref.size(), 0);
   4.127 +
   4.128 +    cxMapDestroy(map);
   4.129 +    EXPECT_TRUE(allocator.verify());
   4.130 +}

mercurial