From 47a98ed1fe911b407e09bd6289cc516ddae709c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Fri, 5 Jun 2026 00:37:12 +0200 Subject: [PATCH] Renamed "emplace" as "quick_emplace" and recover strong exception guarantee for "emplace". --- experimental/bench_hub.cpp | 90 ++++++++++++ include/boost/container/experimental/nest.hpp | 131 ++++++++++++++++-- test/nest_test.cpp | 33 +++++ 3 files changed, 241 insertions(+), 13 deletions(-) diff --git a/experimental/bench_hub.cpp b/experimental/bench_hub.cpp index 8c5e578..3d130ac 100644 --- a/experimental/bench_hub.cpp +++ b/experimental/bench_hub.cpp @@ -188,6 +188,41 @@ BOOST_CONTAINER_FORCEINLINE void erase_void(boost::container::nest& x, x.erase_void(it); } +//quick_emplace: containers without a quick_emplace member (hub, plf::hive) +//fall back to insert; nest uses its faster, capacity-rollback-free path. +template +BOOST_CONTAINER_FORCEINLINE typename Container::iterator +quick_emplace(Container& x, const T& v) +{ + return x.insert(v); +} + +template +BOOST_CONTAINER_FORCEINLINE typename boost::container::nest::iterator +quick_emplace(boost::container::nest& x, const T& v) +{ + return x.quick_emplace(v); +} + +//quick_erase: erase helper mirroring erase_void, used by the quick build path. +template +BOOST_CONTAINER_FORCEINLINE void quick_erase(Container& x, Iterator it) +{ + x.erase(it); +} + +template +BOOST_CONTAINER_FORCEINLINE void quick_erase(boost::container::hub& x, Iterator it) +{ + x.erase_void(it); +} + +template +BOOST_CONTAINER_FORCEINLINE void quick_erase(boost::container::nest& x, Iterator it) +{ + x.erase_void(it); +} + template Container make(std::size_t n, double erasure_rate) { @@ -217,6 +252,37 @@ void fill(Container& c, std::size_t n) } } +//Quick variants of make/fill that exercise the quick_emplace insertion path +//(nest::quick_emplace; insert for the other containers). +template +Container quick_make(std::size_t n, double erasure_rate) +{ + std::uint64_t erasure_cut = + (std::uint64_t)(erasure_rate * (double)(std::uint64_t)(-1)); + + Container c; + urbg rng; + std::vector iterators; + + iterators.reserve(n); + for(std::size_t i = 0; i < n; ++i) iterators.push_back(quick_emplace(c, (int)rng())); + std::shuffle(iterators.begin(), iterators.end(), rng); + for(auto it: iterators) { + if(rng() < erasure_cut) quick_erase(c, it); + } + return c; +} + +template +void quick_fill(Container& c, std::size_t n) +{ + urbg rng; + if(n > c.size()) { + n -= c.size(); + while(n--) quick_emplace(c, (int)rng()); + } +} + /* static std::size_t min_size_exp = 3, max_size_exp = 7; @@ -393,6 +459,27 @@ struct filling } }; +//Like filling, but the measured re-insertion uses the quick_emplace path +//(nest::quick_emplace; insert for hub/hive). +template +struct quick_filling +{ + unsigned int operator()(std::size_t n, double erasure_rate) const + { + unsigned int res = 0; + { + pause_timing(); + auto c = quick_make(n, erasure_rate); // excluded + resume_timing(); + quick_fill(c, n); // measured + res = (unsigned int)c.size(); + pause_timing(); // exclude destruction + } + resume_timing(); + return res; + } +}; + template struct erasure { @@ -602,6 +689,9 @@ run_summary run_bench() t.push_back(benchmark( "filling", element_size, filling{}, filling{})); + t.push_back(benchmark( + "quick filling", element_size, + quick_filling{}, quick_filling{})); t.push_back(benchmark( "erasure", element_size, ::erasure{}, ::erasure{})); diff --git a/include/boost/container/experimental/nest.hpp b/include/boost/container/experimental/nest.hpp index 93046b9..cd0e2f8 100644 --- a/include/boost/container/experimental/nest.hpp +++ b/include/boost/container/experimental/nest.hpp @@ -1575,9 +1575,54 @@ class nest //! //! Returns: An iterator to the inserted element. //! + //! Throws: Nothing unless the element's constructor throws. If it does, + //! the container is left in its original state (strong exception guarantee): + //! any block speculatively allocated for the new element is freed. + //! //! Complexity: Constant (amortized). template BOOST_CONTAINER_FORCEINLINE iterator emplace(BOOST_FWD_REF(Args)... args) + { + block_base_pointer const pbb_prev = blist.next_available; + int n; + block_pointer const pb = priv_retrieve_available_block(n); + this->priv_construct_or_restore_capacity( + boost::movelib::to_raw_pointer(pb->data()) + n, pbb_prev, + boost::forward(args)...); + pb->mask |= pb->mask + 1u; + const mask_type mask_plus_one = pb->mask + 1u; + if (BOOST_UNLIKELY(mask_plus_one <= 2)) { + // pb->mask == 0 (impossible), 1 or full + if (mask_plus_one == 0) blist.unlink_available(pb); + // pb->mask == 1 + else blist.link_at_back(pb); + } + + ++size_; + return iterator(pb, n); + } + + //! Effects: Inserts an element constructed in-place with args (hint ignored). + //! + //! Returns: An iterator to the inserted element. + //! + //! Complexity: Constant (amortized). + template + BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator, BOOST_FWD_REF(Args)... args) + { return emplace(boost::forward(args)...); } + + //! Effects: Inserts an element constructed in-place with args. + //! + //! Returns: An iterator to the inserted element. + //! + //! Throws: Nothing unless the element's constructor throws. + //! Basic exception guarantee. + //! + //! Complexity: Constant (amortized). + //! + //! Note: Experimental API. + template + BOOST_CONTAINER_FORCEINLINE iterator quick_emplace(BOOST_FWD_REF(Args)... args) { int n; block_pointer const pb = priv_retrieve_available_block(n); @@ -1601,20 +1646,33 @@ class nest return iterator(pb, n); } - //! Effects: Inserts an element constructed in-place with args (hint ignored). - //! - //! Returns: An iterator to the inserted element. - //! - //! Complexity: Constant (amortized). - template - BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator, BOOST_FWD_REF(Args)... args) - { return emplace(boost::forward(args)...); } - #else // BOOST_NO_CXX11_VARIADIC_TEMPLATES #define BOOST_CONTAINER_NEST_EMPLACE_CODE(N) \ BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ BOOST_CONTAINER_FORCEINLINE iterator emplace(BOOST_MOVE_UREF##N) \ + { \ + block_base_pointer const pbb_prev = blist.next_available; \ + int n_; \ + block_pointer pb = priv_retrieve_available_block(n_); \ + this->priv_construct_or_restore_capacity( \ + boost::movelib::to_raw_pointer(pb->data() + n_), pbb_prev \ + BOOST_MOVE_I##N BOOST_MOVE_FWD##N); \ + const mask_type mask_plus_one = (pb->mask |= pb->mask + 1u) + 1u; \ + if (BOOST_UNLIKELY(mask_plus_one <= 2)) { \ + if (mask_plus_one == 0) blist.unlink_available(pb); \ + else blist.link_at_back(pb); \ + } \ + ++size_; \ + return iterator(pb, n_); \ + } \ + \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator BOOST_MOVE_I##N BOOST_MOVE_UREF##N) \ + { return emplace(BOOST_MOVE_FWD##N); } \ + \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + BOOST_CONTAINER_FORCEINLINE iterator quick_emplace(BOOST_MOVE_UREF##N) \ { \ int n_; \ block_pointer pb = priv_retrieve_available_block(n_); \ @@ -1629,10 +1687,6 @@ class nest ++size_; \ return iterator(pb, n_); \ } \ - \ - BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ - BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator BOOST_MOVE_I##N BOOST_MOVE_UREF##N) \ - { return emplace(BOOST_MOVE_FWD##N); } \ // BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_NEST_EMPLACE_CODE) #undef BOOST_CONTAINER_NEST_EMPLACE_CODE @@ -1979,6 +2033,57 @@ class nest } } + // If the next_available block at the moment of failure is not the same as the one + // observed before calling priv_retrieve_available_block, a new (and now empty) block + // was freshly allocated for the failed construction: free it to restore capacity. + BOOST_CONTAINER_NOINLINE void priv_restore_capacity_on_throw(block_base_pointer pbb_prev) BOOST_NOEXCEPT + { + if(blist.next_available != pbb_prev) { + block_pointer const pb_new = static_cast_block_pointer(blist.next_available); + blist.unlink_available(pb_new); + priv_delete_block(pb_new); + --num_blocks; + } + } + + // The try/catch lives here (not inline in emplace) on purpose: MSVC will not + // inline a function that contains its own EH scope, so keeping emplace + // EH-clean lets it be inlined into the caller's insert loop. This is a plain + // (non-FORCEINLINE) helper so the EH scope stays out of emplace. + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + inline void priv_construct_or_restore_capacity + (T* p, block_base_pointer pbb_prev, BOOST_FWD_REF(Args)... args) + { + BOOST_TRY{ + block_alloc_traits::construct(al(), p, boost::forward(args)...); + } + BOOST_CATCH(...){ + this->priv_restore_capacity_on_throw(pbb_prev); + BOOST_RETHROW; + } + BOOST_CATCH_END + } + #else + #define BOOST_CONTAINER_NEST_CONSTRUCT_OR_RESTORE_CODE(N) \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + inline void priv_construct_or_restore_capacity \ + (T* p, block_base_pointer pbb_prev BOOST_MOVE_I##N BOOST_MOVE_UREF##N) \ + { \ + BOOST_TRY{ \ + block_alloc_traits::construct(al(), p BOOST_MOVE_I##N BOOST_MOVE_FWD##N); \ + } \ + BOOST_CATCH(...){ \ + this->priv_restore_capacity_on_throw(pbb_prev); \ + BOOST_RETHROW; \ + } \ + BOOST_CATCH_END \ + } \ + // + BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_NEST_CONSTRUCT_OR_RESTORE_CODE) + #undef BOOST_CONTAINER_NEST_CONSTRUCT_OR_RESTORE_CODE + #endif + ////////////////////////////////////////////// // // private: destruction helpers diff --git a/test/nest_test.cpp b/test/nest_test.cpp index 6a6cedb..5012823 100644 --- a/test/nest_test.cpp +++ b/test/nest_test.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include using namespace boost::container; @@ -93,6 +95,36 @@ void test_emplace() BOOST_TEST_EQ(h.size(), 1u); } +void test_quick_emplace() +{ + nest h; + nest::iterator it = h.quick_emplace(0); + BOOST_TEST_EQ(*it, 0); + BOOST_TEST_EQ(h.size(), 1u); + + //Fill enough elements to span several blocks and verify all are present. + const int count = 1000; + for(int i = 1; i < count; ++i){ + nest::iterator jt = h.quick_emplace(i); + BOOST_TEST_EQ(*jt, i); + } + BOOST_TEST_EQ(h.size(), (std::size_t)count); + + //quick_emplace and emplace must interoperate on the same container. + h.emplace(-1); + h.quick_emplace(-2); + BOOST_TEST_EQ(h.size(), (std::size_t)(count + 2)); + + //All originally inserted values [0, count) must still be retrievable. + std::vector seen(count, false); + for(nest::iterator b = h.begin(), e = h.end(); b != e; ++b){ + if(*b >= 0 && *b < count) + seen[(std::size_t)*b] = true; + } + for(int i = 0; i < count; ++i) + BOOST_TEST(seen[(std::size_t)i]); +} + void test_assign() { nest h(5, 1); @@ -727,6 +759,7 @@ int main() test_move_construction(); test_insert_erase(); test_emplace(); + test_quick_emplace(); test_assign(); test_copy_assignment(); test_move_assignment();