From 503eab84d5a821c07b24094edad6a46ac90dc2b6 Mon Sep 17 00:00:00 2001 From: "Gudmundur F. Adalsteinsson" Date: Mon, 29 Aug 2022 16:12:55 +0000 Subject: [PATCH 1/5] object::stable_erase --- include/boost/json/impl/object.ipp | 53 ++++++++++++++++++++++++++++ include/boost/json/object.hpp | 45 ++++++++++++++++++++++++ test/object.cpp | 56 ++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/include/boost/json/impl/object.ipp b/include/boost/json/impl/object.ipp index 1a0c62e91..5890bec7e 100644 --- a/include/boost/json/impl/object.ipp +++ b/include/boost/json/impl/object.ipp @@ -556,6 +556,59 @@ erase(string_view key) noexcept -> return 1; } +auto +object:: +stable_erase(const_iterator pos) noexcept -> + iterator +{ + auto p = begin() + (pos - begin()); + if(t_->is_small()) + { + p->~value_type(); + --t_->size; + if(p != end()) + { + // the casts silence warnings + std::memmove( + static_cast(p), + static_cast(p + 1), + sizeof(*p) * (end() - p)); + } + return p; + } + remove(t_->bucket(p->key()), *p); + p->~value_type(); + --t_->size; + auto pret = p; + for (; p != end(); ++p) + { + auto const pb = p + 1; + auto& head = t_->bucket(pb->key()); + remove(head, *pb); + // the casts silence warnings + std::memcpy( + static_cast(p), + static_cast(pb), + sizeof(*p)); + access::next(*p) = head; + head = static_cast< + index_t>(p - begin()); + } + return pret; +} + +auto +object:: +stable_erase(string_view key) noexcept -> + std::size_t +{ + auto it = find(key); + if(it == end()) + return 0; + stable_erase(it); + return 1; +} + void object:: swap(object& other) diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index 21727d08c..d691af048 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1166,6 +1166,51 @@ class object std::size_t erase(string_view key) noexcept; + /** Erase an element preserving order + + Remove the element pointed to by `pos`, which must + be valid and dereferenceable. Thus the @ref end() + iterator (which is valid but cannot be dereferenced) + cannot be used as a value for `pos`. + All references and iterators are invalidated. + The relative order of non-erased elements is preserved. + + @par Complexity + Linear in @ref size(). + + @par Exception Safety + No-throw guarantee. + + @return An iterator following the last removed element. + + @param pos An iterator pointing to the element to be + removed. + */ + BOOST_JSON_DECL + iterator + stable_erase(const_iterator pos) noexcept; + + /** Erase an element preserving order + + Remove the element which matches `key`, if it exists. + All references and iterators are invalidated. + The relative order of non-erased elements is preserved. + + @par Complexity + Linear in @ref size(). + + @par Exception Safety + No-throw guarantee. + + @return The number of elements removed, which will + be either 0 or 1. + + @param key The key to match. + */ + BOOST_JSON_DECL + std::size_t + stable_erase(string_view key) noexcept; + /** Swap two objects. Exchanges the contents of this object with another diff --git a/test/object.cpp b/test/object.cpp index 1186fa6dd..8933d0e0f 100644 --- a/test/object.cpp +++ b/test/object.cpp @@ -1204,6 +1204,62 @@ class object_test } } + // stable_erase(pos) + { + // small + { + object o(i0_); + auto it = o.stable_erase(o.find("10")); + BOOST_TEST(it->key() == "11"); + BOOST_TEST( + it->value().as_int64() == 11); + BOOST_TEST(serialize(o) == + R"({"0":0,"1":1,"2":2,"3":3,"4":4,)" + R"("5":5,"6":6,"7":7,"8":8,"9":9,)" + R"("11":11,"12":12,"13":13,"14":14,"15":15})"); + BOOST_TEST(o.find("11") == it); + BOOST_TEST(o.find("14") == o.end() - 2); + } + + // large + { + object o(i1_); + auto it = o.stable_erase(o.find("10")); + BOOST_TEST(it->key() == "11"); + BOOST_TEST( + it->value().as_int64() == 11); + BOOST_TEST(serialize(o) == + R"({"0":0,"1":1,"2":2,"3":3,"4":4,)" + R"("5":5,"6":6,"7":7,"8":8,"9":9,)" + R"("11":11,"12":12,"13":13,"14":14,"15":15,)" + R"("16":16,"17":17,"18":18,"19":19})"); + BOOST_TEST(o.find("11") == it); + BOOST_TEST(o.find("18") == o.end() - 2); + } + } + + // stable_erase(key) + { + { + object o({ + {"a", 1}, + {"b", true}, + {"c", "hello"}}); + BOOST_TEST(o.stable_erase("b2") == 0); + check(o, 3); + } + + { + object o({ + {"a", 1}, + {"b", true}, + {"b2", 2}, + {"c", "hello"}}); + BOOST_TEST(o.stable_erase("b2") == 1); + check(o, 4); + } + } + // swap(object&) { { From 7ac8cae9d3a6e0204d83b36449819061e8b7d4ab Mon Sep 17 00:00:00 2001 From: "Gudmundur F. Adalsteinsson" Date: Mon, 29 Aug 2022 17:19:55 +0000 Subject: [PATCH 2/5] Refactor out relocation function --- include/boost/json/impl/object.ipp | 43 +++++++++++++++--------------- include/boost/json/object.hpp | 6 +++++ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/include/boost/json/impl/object.ipp b/include/boost/json/impl/object.ipp index 5890bec7e..6c89762a2 100644 --- a/include/boost/json/impl/object.ipp +++ b/include/boost/json/impl/object.ipp @@ -527,19 +527,9 @@ erase(const_iterator pos) noexcept -> remove(t_->bucket(p->key()), *p); p->~value_type(); --t_->size; - auto const pb = end(); if(p != end()) { - auto& head = t_->bucket(pb->key()); - remove(head, *pb); - // the casts silence warnings - std::memcpy( - static_cast(p), - static_cast(pb), - sizeof(*p)); - access::next(*p) = head; - head = static_cast< - index_t>(p - begin()); + reindex_relocate(end(), p); } return p; } @@ -582,17 +572,7 @@ stable_erase(const_iterator pos) noexcept -> auto pret = p; for (; p != end(); ++p) { - auto const pb = p + 1; - auto& head = t_->bucket(pb->key()); - remove(head, *pb); - // the casts silence warnings - std::memcpy( - static_cast(p), - static_cast(pb), - sizeof(*p)); - access::next(*p) = head; - head = static_cast< - index_t>(p - begin()); + reindex_relocate(p + 1, p); } return pret; } @@ -883,6 +863,25 @@ destroy( (--last)->~key_value_pair(); } +void +object:: +reindex_relocate( + key_value_pair* src, + key_value_pair* dst) noexcept +{ + BOOST_ASSERT(! t_->is_small()); + auto& head = t_->bucket(src->key()); + remove(head, *src); + // the casts silence warnings + std::memcpy( + static_cast(dst), + static_cast(src), + sizeof(*dst)); + access::next(*dst) = head; + head = static_cast< + index_t>(dst - begin()); +} + BOOST_JSON_NS_END //---------------------------------------------------------- diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index d691af048..748c035fd 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1612,6 +1612,12 @@ class object destroy( key_value_pair* first, key_value_pair* last) noexcept; + + inline + void + reindex_relocate( + key_value_pair* src, + key_value_pair* dst) noexcept; }; BOOST_JSON_NS_END From 1b3eb61c57e251f71ed2e0d89d6a33ea4816a60e Mon Sep 17 00:00:00 2001 From: "Gudmundur F. Adalsteinsson" Date: Mon, 29 Aug 2022 20:04:21 +0000 Subject: [PATCH 3/5] More precise invalidation specification --- include/boost/json/object.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index 748c035fd..41dfb0367 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1172,7 +1172,9 @@ class object be valid and dereferenceable. Thus the @ref end() iterator (which is valid but cannot be dereferenced) cannot be used as a value for `pos`. - All references and iterators are invalidated. + References and iterators from `pos` to `end()`, both + included, are invalidated. Other iterators and references + are not invalidated. The relative order of non-erased elements is preserved. @par Complexity From e1088609c20a97dca13f859d22066311cbc2cb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0mundur=20F=2E=20A=C3=B0alsteinsson?= Date: Fri, 2 Sep 2022 13:11:33 +0000 Subject: [PATCH 4/5] Improve erase and stable_erase documentation --- include/boost/json/object.hpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index 41dfb0367..378f2d9a5 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1122,20 +1122,23 @@ class object /** Erase an element Remove the element pointed to by `pos`, which must - be valid and dereferenceable. Thus the @ref end() - iterator (which is valid but cannot be dereferenced) - cannot be used as a value for `pos`. + be valid and dereferenceable. References and iterators to the erased element are invalidated. Other iterators and references are not invalidated. + @note + + The @ref end() iterator (which is valid but cannot be + dereferenced) cannot be used as a value for `pos`. + @par Complexity Constant on average, worst case linear in @ref size(). @par Exception Safety No-throw guarantee. - @return An iterator following the last removed element. + @return An iterator following the removed element. @param pos An iterator pointing to the element to be removed. @@ -1169,21 +1172,24 @@ class object /** Erase an element preserving order Remove the element pointed to by `pos`, which must - be valid and dereferenceable. Thus the @ref end() - iterator (which is valid but cannot be dereferenced) - cannot be used as a value for `pos`. + be valid and dereferenceable. References and iterators from `pos` to `end()`, both included, are invalidated. Other iterators and references are not invalidated. The relative order of non-erased elements is preserved. + @note + + The @ref end() iterator (which is valid but cannot be + dereferenced) cannot be used as a value for `pos`. + @par Complexity Linear in @ref size(). @par Exception Safety No-throw guarantee. - @return An iterator following the last removed element. + @return An iterator following the removed element. @param pos An iterator pointing to the element to be removed. From 47f2ad55096cd4c23d69acea482ceff9ed8431fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0mundur=20F=2E=20A=C3=B0alsteinsson?= Date: Fri, 16 Sep 2022 20:25:28 +0000 Subject: [PATCH 5/5] Refactor object::erase --- include/boost/json/impl/object.ipp | 85 ++++++++++++++++-------------- include/boost/json/object.hpp | 12 ++++- 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/include/boost/json/impl/object.ipp b/include/boost/json/impl/object.ipp index 6c89762a2..1e395c0e3 100644 --- a/include/boost/json/impl/object.ipp +++ b/include/boost/json/impl/object.ipp @@ -508,30 +508,17 @@ object:: erase(const_iterator pos) noexcept -> iterator { - auto p = begin() + (pos - begin()); - if(t_->is_small()) - { - p->~value_type(); - --t_->size; - auto const pb = end(); - if(p != end()) - { + return do_erase(pos, + [this](iterator p) { // the casts silence warnings std::memcpy( static_cast(p), - static_cast(pb), + static_cast(end()), sizeof(*p)); - } - return p; - } - remove(t_->bucket(p->key()), *p); - p->~value_type(); - --t_->size; - if(p != end()) - { - reindex_relocate(end(), p); - } - return p; + }, + [this](iterator p) { + reindex_relocate(end(), p); + }); } auto @@ -551,30 +538,20 @@ object:: stable_erase(const_iterator pos) noexcept -> iterator { - auto p = begin() + (pos - begin()); - if(t_->is_small()) - { - p->~value_type(); - --t_->size; - if(p != end()) - { + return do_erase(pos, + [this](iterator p) { // the casts silence warnings std::memmove( static_cast(p), static_cast(p + 1), sizeof(*p) * (end() - p)); - } - return p; - } - remove(t_->bucket(p->key()), *p); - p->~value_type(); - --t_->size; - auto pret = p; - for (; p != end(); ++p) - { - reindex_relocate(p + 1, p); - } - return pret; + }, + [this](iterator p) { + for (; p != end(); ++p) + { + reindex_relocate(p + 1, p); + } + }); } auto @@ -863,6 +840,36 @@ destroy( (--last)->~key_value_pair(); } +template +auto +object:: +do_erase( + const_iterator pos, + FS small_reloc, + FB big_reloc) noexcept + -> iterator +{ + auto p = begin() + (pos - begin()); + if(t_->is_small()) + { + p->~value_type(); + --t_->size; + if(p != end()) + { + small_reloc(p); + } + return p; + } + remove(t_->bucket(p->key()), *p); + p->~value_type(); + --t_->size; + if(p != end()) + { + big_reloc(p); + } + return p; +} + void object:: reindex_relocate( diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index 378f2d9a5..2922079c2 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1176,7 +1176,7 @@ class object References and iterators from `pos` to `end()`, both included, are invalidated. Other iterators and references are not invalidated. - The relative order of non-erased elements is preserved. + The relative order of remaining elements is preserved. @note @@ -1202,7 +1202,7 @@ class object Remove the element which matches `key`, if it exists. All references and iterators are invalidated. - The relative order of non-erased elements is preserved. + The relative order of remaining elements is preserved. @par Complexity Linear in @ref size(). @@ -1621,6 +1621,14 @@ class object key_value_pair* first, key_value_pair* last) noexcept; + template + auto + do_erase( + const_iterator pos, + FS small_reloc, + FB big_reloc) noexcept + -> iterator; + inline void reindex_relocate(