Quellcode durchsuchen

dense_set: uses-allocator construction

Michele Caini vor 4 Jahren
Ursprung
Commit
3f1dee2650
2 geänderte Dateien mit 83 neuen und 18 gelöschten Zeilen
  1. 59 17
      src/entt/container/dense_set.hpp
  2. 24 1
      test/entt/container/dense_set.cpp

+ 59 - 17
src/entt/container/dense_set.hpp

@@ -27,22 +27,63 @@ namespace entt {
 
 namespace internal {
 
-template<typename Type>
-struct dense_set_node final {
+template<typename Type, typename Allocator>
+struct dense_set_node final: Allocator {
+    using allocator_type = Allocator;
+
     template<typename... Args>
     dense_set_node(const std::size_t pos, Args &&...args)
-        : next{pos},
-          element{std::forward<Args>(args)...} {}
+        : dense_set_node{std::allocator_arg, Allocator{}, pos, std::forward<Args>(args)...} {
+    }
+
+    template<typename... Args>
+    dense_set_node(std::allocator_arg_t, const allocator_type &allocator, const std::size_t pos, Args &&...args)
+        : Allocator{allocator},
+          next{pos} {
+        std::allocator_traits<allocator_type>::construct(*this, reinterpret_cast<Type *>(&instance), std::forward<Args>(args)...);
+    }
+
+    dense_set_node(const dense_set_node &other)
+        : dense_set_node{std::allocator_arg, Allocator{}, other} {
+    }
+
+    dense_set_node(std::allocator_arg_t, const allocator_type &allocator, const dense_set_node &other)
+        : Allocator{allocator},
+          next{other.next} {
+        std::allocator_traits<allocator_type>::construct(*this, reinterpret_cast<Type *>(&instance), other.element());
+    }
+
+    dense_set_node(dense_set_node &&other) ENTT_NOEXCEPT
+        : dense_set_node{std::allocator_arg, Allocator{}, std::move(other)} {
+    }
+
+    dense_set_node(std::allocator_arg_t, const allocator_type &allocator, dense_set_node &&other) ENTT_NOEXCEPT
+        : Allocator{allocator},
+          next{other.next} {
+        std::allocator_traits<allocator_type>::construct(*this, reinterpret_cast<Type *>(&instance), std::move(other.element()));
+    }
+
+    ~dense_set_node() ENTT_NOEXCEPT {
+        std::destroy_at(reinterpret_cast<const Type *>(&instance));
+    }
+
+    const Type &element() const ENTT_NOEXCEPT {
+        return *reinterpret_cast<const Type *>(&instance);
+    }
+
+    Type &element() ENTT_NOEXCEPT {
+        return *reinterpret_cast<Type *>(&instance);
+    }
 
     std::size_t next;
-    Type element;
+    std::aligned_storage_t<sizeof(Type)> instance;
 };
 
 template<typename It>
 class dense_set_iterator final {
     friend dense_set_iterator<const std::remove_pointer_t<It> *>;
 
-    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element())))>;
 
 public:
     using value_type = typename iterator_traits::value_type;
@@ -97,11 +138,11 @@ public:
     }
 
     [[nodiscard]] reference operator[](const difference_type value) const {
-        return it[value].element;
+        return it[value].element();
     }
 
     [[nodiscard]] pointer operator->() const {
-        return std::addressof(it->element);
+        return std::addressof(it->element());
     }
 
     [[nodiscard]] reference operator*() const {
@@ -160,7 +201,7 @@ template<typename It>
 class dense_set_local_iterator final {
     friend dense_set_local_iterator<const std::remove_pointer_t<It> *>;
 
-    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element())))>;
 
 public:
     using value_type = typename iterator_traits::value_type;
@@ -190,7 +231,7 @@ public:
     }
 
     [[nodiscard]] pointer operator->() const {
-        return std::addressof(it[offset].element);
+        return std::addressof(it[offset].element());
     }
 
     [[nodiscard]] reference operator*() const {
@@ -243,7 +284,7 @@ class dense_set {
     using alloc_traits = std::allocator_traits<Allocator>;
     static_assert(std::is_same_v<typename alloc_traits::value_type, Type>);
 
-    using node_type = internal::dense_set_node<Type>;
+    using node_type = internal::dense_set_node<Type, Allocator>;
     using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
     using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
 
@@ -276,7 +317,7 @@ class dense_set {
     template<typename Other>
     bool do_erase(const Other &value) {
         for(size_type *curr = sparse.first().data() + bucket(value); *curr != std::numeric_limits<size_type>::max(); curr = &packed.first()[*curr].next) {
-            if(packed.second()(packed.first()[*curr].element, value)) {
+            if(packed.second()(packed.first()[*curr].element(), value)) {
                 const auto index = *curr;
                 *curr = packed.first()[*curr].next;
                 move_and_pop(index);
@@ -289,12 +330,13 @@ class dense_set {
 
     void move_and_pop(const std::size_t pos) {
         if(const auto last = size() - 1u; pos != last) {
-            size_type *curr = sparse.first().data() + bucket(packed.first().back().element);
+            size_type *curr = sparse.first().data() + bucket(packed.first().back().element());
             for(; *curr != last; curr = &packed.first()[*curr].next) {}
             *curr = pos;
 
             // basic exception guarantees when value type has a throwing move constructor
-            packed.first()[pos] = std::move(packed.first().back());
+            packed.first()[pos].element() = std::move(packed.first().back().element());
+            packed.first()[pos].next = packed.first().back().next;
         }
 
         packed.first().pop_back();
@@ -550,10 +592,10 @@ public:
     template<typename... Args>
     std::pair<iterator, bool> emplace(Args &&...args) {
         auto &node = packed.first().emplace_back(packed.first().size(), std::forward<Args>(args)...);
-        const auto hash = sparse.second()(node.element);
+        const auto hash = sparse.second()(node.element());
         auto index = hash_to_bucket(hash);
 
-        if(auto it = constrained_find(node.element, index); it != end()) {
+        if(auto it = constrained_find(node.element(), index); it != end()) {
             packed.first().pop_back();
             return std::make_pair(it, false);
         }
@@ -801,7 +843,7 @@ public:
             std::fill(sparse.first().begin(), sparse.first().end(), std::numeric_limits<size_type>::max());
 
             for(size_type pos{}, last = size(); pos < last; ++pos) {
-                const auto index = bucket(packed.first()[pos].element);
+                const auto index = bucket(packed.first()[pos].element());
                 packed.first()[pos].next = std::exchange(sparse.first()[index], pos);
             }
         }

+ 24 - 1
test/entt/container/dense_set.cpp

@@ -10,6 +10,7 @@
 #include <entt/core/memory.hpp>
 #include <entt/core/utility.hpp>
 #include "../common/throwing_allocator.hpp"
+#include "../common/tracked_memory_resource.hpp"
 
 struct transparent_equal_to {
     using is_transparent = void;
@@ -820,7 +821,7 @@ TEST(DenseSet, Reserve) {
 
 TEST(DenseSet, ThrowingAllocator) {
     using allocator = test::throwing_allocator<std::size_t>;
-    using packed_allocator = test::throwing_allocator<entt::internal::dense_set_node<std::size_t>>;
+    using packed_allocator = test::throwing_allocator<entt::internal::dense_set_node<std::size_t, test::throwing_allocator<std::size_t>>>;
     using packed_exception = typename packed_allocator::exception_type;
 
     static constexpr std::size_t minimum_bucket_count = 8u;
@@ -832,3 +833,25 @@ TEST(DenseSet, ThrowingAllocator) {
     ASSERT_THROW(set.reserve(2u * set.bucket_count()), packed_exception);
     ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
 }
+
+#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE)
+
+TEST(DenseSet, UsesAllocatorConstruction) {
+    using string_type = typename test::tracked_memory_resource::string_type;
+    using allocator = std::pmr::polymorphic_allocator<string_type>;
+
+    test::tracked_memory_resource memory_resource{};
+    entt::dense_set<string_type, std::hash<string_type>, std::equal_to<string_type>, allocator> set{&memory_resource};
+    const char *str = "a string long enough to force an allocation (hopefully)";
+
+    set.reserve(1u);
+    memory_resource.reset();
+    set.emplace(str);
+
+    ASSERT_GT(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+
+    entt::dense_set<string_type, std::hash<string_type>, std::equal_to<string_type>, allocator> other{std::move(set)};
+}
+
+#endif