Renamed "emplace" as "quick_emplace" and recover strong exception guarantee for "emplace".

This commit is contained in:
Ion Gaztañaga
2026-06-05 00:37:12 +02:00
parent b42d9e061b
commit 47a98ed1fe
3 changed files with 241 additions and 13 deletions
+90
View File
@@ -188,6 +188,41 @@ BOOST_CONTAINER_FORCEINLINE void erase_void(boost::container::nest<Args...>& 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<typename Container, typename T>
BOOST_CONTAINER_FORCEINLINE typename Container::iterator
quick_emplace(Container& x, const T& v)
{
return x.insert(v);
}
template<typename... Args, typename T>
BOOST_CONTAINER_FORCEINLINE typename boost::container::nest<Args...>::iterator
quick_emplace(boost::container::nest<Args...>& x, const T& v)
{
return x.quick_emplace(v);
}
//quick_erase: erase helper mirroring erase_void, used by the quick build path.
template<typename Container, typename Iterator>
BOOST_CONTAINER_FORCEINLINE void quick_erase(Container& x, Iterator it)
{
x.erase(it);
}
template<typename... Args, typename Iterator>
BOOST_CONTAINER_FORCEINLINE void quick_erase(boost::container::hub<Args...>& x, Iterator it)
{
x.erase_void(it);
}
template<typename... Args, typename Iterator>
BOOST_CONTAINER_FORCEINLINE void quick_erase(boost::container::nest<Args...>& x, Iterator it)
{
x.erase_void(it);
}
template<typename Container>
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<typename Container>
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<typename Container::iterator> 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<typename Container>
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<typename Container>
struct quick_filling
{
unsigned int operator()(std::size_t n, double erasure_rate) const
{
unsigned int res = 0;
{
pause_timing();
auto c = quick_make<Container>(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<typename Container>
struct erasure
{
@@ -602,6 +689,9 @@ run_summary run_bench()
t.push_back(benchmark(
"filling", element_size,
filling<num>{}, filling<den>{}));
t.push_back(benchmark(
"quick filling", element_size,
quick_filling<num>{}, quick_filling<den>{}));
t.push_back(benchmark(
"erasure", element_size,
::erasure<num>{}, ::erasure<den>{}));
+118 -13
View File
@@ -1575,9 +1575,54 @@ class nest
//!
//! <b>Returns</b>: An iterator to the inserted element.
//!
//! <b>Throws</b>: 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.
//!
//! <b>Complexity</b>: Constant (amortized).
template<class ...Args>
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>(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);
}
//! <b>Effects</b>: Inserts an element constructed in-place with args (hint ignored).
//!
//! <b>Returns</b>: An iterator to the inserted element.
//!
//! <b>Complexity</b>: Constant (amortized).
template<class ...Args>
BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator, BOOST_FWD_REF(Args)... args)
{ return emplace(boost::forward<Args>(args)...); }
//! <b>Effects</b>: Inserts an element constructed in-place with args.
//!
//! <b>Returns</b>: An iterator to the inserted element.
//!
//! <b>Throws</b>: Nothing unless the element's constructor throws.
//! Basic exception guarantee.
//!
//! <b>Complexity</b>: Constant (amortized).
//!
//! <b>Note</b>: Experimental API.
template<class ...Args>
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);
}
//! <b>Effects</b>: Inserts an element constructed in-place with args (hint ignored).
//!
//! <b>Returns</b>: An iterator to the inserted element.
//!
//! <b>Complexity</b>: Constant (amortized).
template<class ...Args>
BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator, BOOST_FWD_REF(Args)... args)
{ return emplace(boost::forward<Args>(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<class ...Args>
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>(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
+33
View File
@@ -12,6 +12,8 @@
#include <boost/core/lightweight_test.hpp>
#include <algorithm>
#include <functional>
#include <vector>
#include <cstddef>
using namespace boost::container;
@@ -93,6 +95,36 @@ void test_emplace()
BOOST_TEST_EQ(h.size(), 1u);
}
void test_quick_emplace()
{
nest<int> h;
nest<int>::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<int>::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<bool> seen(count, false);
for(nest<int>::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<int> 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();