Hi
Here is a patch to enhance the _Hashtable extract node API and fix
a FIXME request.
The enhancement to the extract node Api is that extract(const
key_type&) do not call extract(const_iterator) anymore. Doing so we had
to loop again through bucket nodes to find the previous node to the one
to extract. Even if a bucket shall not contain many nodes (in unique key
mode) it's easy to avoid it.
To fix the FIXME I introduced a node smart pointer type managing
the node lifetime. The node is extracted from this smart pointer only
when there can't be any exception raised. In the context of the node
extract api the node handle is considered as a smart pointer. So the
node handle will remain owner of the node in case of exception when
reinserting it, I hope it is the expected behavior.
* include/bits/hashtable_policy.h
(struct _NodeSmartPointer<_NodeAlloc>): New.
(_Map_base<>::operator[](const key_type&)): Use latter, adapt.
(_Map_base<>::operator[](key_type&&)): Likewise.
* include/bits/hashtable.h
(_Hashtable<>::__node_sp_t): New.
(_Hashtable<>::_M_insert_unique_node(size_type, __hash_code,
__node_type*, size_type)): Replace by...
(_Hashtable<>::_M_insert_unique_node<_NodeAccessor>(const key_type&,
size_type, __hash_code, const _NodeAccessor&, size_type)): ...that.
(_Hashtable<>::_M_insert_multi_node(__node_type*, __hash_code,
__node_type*)): Replace by...
(_Hashtable<>::_M_insert_multi_node<_NodeAccessor>(__node_type*,
__hash_code, const _NodeAccessor&)): ...that.
(_Hashtable<>::_M_reinsert_node): Adapt.
(_Hashtable<>::_M_reinsert_node_multi): Adapt.
(_Hashtable<>::_M_extract_node(size_t, __node_base*)): New.
(_Hashtable<>::extract(const_iterator)): Use latter.
(_Hashtable<>::extract(const _Key&)): Likewise.
(_Hashtable<>::_M_merge_unique): Adapt.
(_Hashtable<>::_M_emplace<_Args>(true_type, _Args&&...)): Adapt.
(_Hashtable<>::_M_emplace<_Args>(const_iterator, false_type,
_Args&&...)): Adapt.
Tested under Linux x86_64.
Ok to commit ?
François
diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index e2e3f016a35..307865b96bf 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -197,6 +197,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
using __hash_cached = typename __traits_type::__hash_cached;
using __node_type = __detail::_Hash_node<_Value, __hash_cached::value>;
using __node_alloc_type = __alloc_rebind<_Alloc, __node_type>;
+ using __node_sp_t = __detail::_NodeSmartPointer<__node_alloc_type>;
using __hashtable_alloc = __detail::_Hashtable_alloc<__node_alloc_type>;
@@ -669,18 +670,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__node_base*
_M_get_previous_node(size_type __bkt, __node_base* __n);
- // Insert node with hash code __code, in bucket bkt if no rehash (assumes
- // no element with its key already present). Take ownership of the node,
- // deallocate it on exception.
+ // Insert node with key __k and hash code __code, in bucket __bkt if no
+ // rehash (assumes no element with its key already present).
+ template<typename _NodeAccessor>
iterator
- _M_insert_unique_node(size_type __bkt, __hash_code __code,
- __node_type* __n, size_type __n_elt = 1);
+ _M_insert_unique_node(const key_type& __k, size_type __bkt,
+ __hash_code __code, const _NodeAccessor&,
+ size_type __n_elt = 1);
- // Insert node with hash code __code. Take ownership of the node,
- // deallocate it on exception.
+ // Insert node with hash code __code.
+ template<typename _NodeAccessor>
iterator
- _M_insert_multi_node(__node_type* __hint,
- __hash_code __code, __node_type* __n);
+ _M_insert_multi_node(__node_type* __hint, __hash_code __code,
+ const _NodeAccessor& __node_accessor);
template<typename... _Args>
std::pair<iterator, bool>
@@ -805,9 +807,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
else
{
- __ret.position
- = _M_insert_unique_node(__bkt, __code, __nh._M_ptr);
- __nh._M_ptr = nullptr;
+ __ret.position = _M_insert_unique_node(__k, __bkt, __code,
+ [&__nh]()
+ { return std::exchange(__nh._M_ptr, nullptr); });
__ret.inserted = true;
}
}
@@ -818,33 +820,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
iterator
_M_reinsert_node_multi(const_iterator __hint, node_type&& __nh)
{
- iterator __ret;
if (__nh.empty())
- __ret = end();
- else
- {
+ return end();
+
__glibcxx_assert(get_allocator() == __nh.get_allocator());
auto __code = this->_M_hash_code(__nh._M_key());
- auto __node = std::exchange(__nh._M_ptr, nullptr);
- // FIXME: this deallocates the node on exception.
- __ret = _M_insert_multi_node(__hint._M_cur, __code, __node);
- }
- return __ret;
+ return _M_insert_multi_node(__hint._M_cur, __code,
+ [&__nh]()
+ { return std::exchange(__nh._M_ptr, nullptr); });
}
+ private:
/// Extract a node.
node_type
- extract(const_iterator __pos)
+ _M_extract_node(size_t __bkt, __node_base* __prev_n)
{
- __node_type* __n = __pos._M_cur;
- size_t __bkt = _M_bucket_index(__n);
-
- // Look for previous node to unlink it from the erased one, this
- // is why we need buckets to contain the before begin to make
- // this search fast.
- __node_base* __prev_n = _M_get_previous_node(__bkt, __n);
-
+ __node_type* __n = static_cast<__node_type*>(__prev_n->_M_nxt);
if (__prev_n == _M_buckets[__bkt])
_M_remove_bucket_begin(__bkt, __n->_M_next(),
__n->_M_nxt ? _M_bucket_index(__n->_M_next()) : 0);
@@ -861,14 +853,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
return { __n, this->_M_node_allocator() };
}
+ public:
+ /// Extract a node.
+ node_type
+ extract(const_iterator __pos)
+ {
+ size_t __bkt = _M_bucket_index(__pos._M_cur);
+ return _M_extract_node(__bkt, _M_get_previous_node(__bkt, __pos._M_cur));
+ }
+
/// Extract a node.
node_type
extract(const _Key& __k)
{
node_type __nh;
- auto __pos = find(__k);
- if (__pos != end())
- __nh = extract(const_iterator(__pos));
+ __hash_code __code = this->_M_hash_code(__k);
+ std::size_t __bkt = _M_bucket_index(__k, __code);
+ if (__node_base* __prev_node = _M_find_before_node(__bkt, __k, __code))
+ __nh = _M_extract_node(__bkt, __prev_node);
return __nh;
}
@@ -885,14 +887,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
{
auto __pos = __i++;
- const key_type& __k = this->_M_extract()(__pos._M_cur->_M_v());
+ const key_type& __k = this->_M_extract()(*__pos);
__hash_code __code = this->_M_hash_code(__k);
size_type __bkt = _M_bucket_index(__k, __code);
if (_M_find_node(__bkt, __k, __code) == nullptr)
{
auto __nh = __src.extract(__pos);
- _M_insert_unique_node(__bkt, __code, __nh._M_ptr, __n_elt);
- __nh._M_ptr = nullptr;
+ _M_insert_unique_node(__k, __bkt, __code,
+ [&__nh]()
+ { return std::exchange(__nh._M_ptr, nullptr); },
+ __n_elt);
__n_elt = 1;
}
else if (__n_elt != 1)
@@ -1634,31 +1638,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
-> pair<iterator, bool>
{
// First build the node to get access to the hash code
- __node_type* __node
- = this->_M_allocate_node(std::forward<_Args>(__args)...);
- const key_type& __k = this->_M_extract()(__node->_M_v());
- __hash_code __code;
- __try
- {
- __code = this->_M_hash_code(__k);
- }
- __catch(...)
- {
- this->_M_deallocate_node(__node);
- __throw_exception_again;
- }
+ __node_sp_t __node_sp(*this,
+ this->_M_allocate_node(std::forward<_Args>(__args)...));
+ const key_type& __k
+ = this->_M_extract()(__node_sp._M_get()->_M_v());
+ __hash_code __code = this->_M_hash_code(__k);
size_type __bkt = _M_bucket_index(__k, __code);
- if (__node_type* __p = _M_find_node(__bkt, __k, __code))
- {
+ if (__node_type* __node = _M_find_node(__bkt, __k, __code))
// There is already an equivalent node, no insertion
- this->_M_deallocate_node(__node);
- return std::make_pair(iterator(__p), false);
- }
+ return { iterator(__node), false };
// Insert the node
- return std::make_pair(_M_insert_unique_node(__bkt, __code, __node),
- true);
+ return
+ {
+ _M_insert_unique_node(__k, __bkt, __code,
+ [&__node_sp]()
+ { return __node_sp._M_release(); }),
+ true
+ };
}
template<typename _Key, typename _Value,
@@ -1673,32 +1671,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
-> iterator
{
// First build the node to get its hash code.
- __node_type* __node =
- this->_M_allocate_node(std::forward<_Args>(__args)...);
+ __node_sp_t __node_sp(*this,
+ this->_M_allocate_node(std::forward<_Args>(__args)...));
- __hash_code __code;
- __try
- {
- __code = this->_M_hash_code(this->_M_extract()(__node->_M_v()));
- }
- __catch(...)
- {
- this->_M_deallocate_node(__node);
- __throw_exception_again;
- }
-
- return _M_insert_multi_node(__hint._M_cur, __code, __node);
+ const key_type& __k
+ = this->_M_extract()(__node_sp._M_get()->_M_v());
+ return _M_insert_multi_node(__hint._M_cur, this->_M_hash_code(__k),
+ [&__node_sp]()
+ { return __node_sp._M_release(); });
}
template<typename _Key, typename _Value,
typename _Alloc, typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,
typename _Traits>
+ template<typename _NodeAccessor>
auto
_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>::
- _M_insert_unique_node(size_type __bkt, __hash_code __code,
- __node_type* __node, size_type __n_elt)
+ _M_insert_unique_node(const key_type& __k, size_type __bkt,
+ __hash_code __code,
+ const _NodeAccessor& __node_accessor,
+ size_type __n_elt)
-> iterator
{
const __rehash_state& __saved_state = _M_rehash_policy._M_state();
@@ -1706,15 +1700,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
= _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count,
__n_elt);
- __try
- {
if (__do_rehash.first)
{
_M_rehash(__do_rehash.second, __saved_state);
- __bkt
- = _M_bucket_index(this->_M_extract()(__node->_M_v()), __code);
+ __bkt = _M_bucket_index(__k, __code);
}
+ __node_type* __node = __node_accessor();
this->_M_store_code(__node, __code);
// Always insert at the beginning of the bucket.
@@ -1722,12 +1714,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
++_M_element_count;
return iterator(__node);
}
- __catch(...)
- {
- this->_M_deallocate_node(__node);
- __throw_exception_again;
- }
- }
// Insert node, in bucket bkt if no rehash (assumes no element with its key
// already present). Take ownership of the node, deallocate it on exception.
@@ -1735,22 +1721,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
typename _Alloc, typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,
typename _Traits>
+ template<typename _NodeAccessor>
auto
_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>::
_M_insert_multi_node(__node_type* __hint, __hash_code __code,
- __node_type* __node)
+ const _NodeAccessor& __node_accessor)
-> iterator
{
const __rehash_state& __saved_state = _M_rehash_policy._M_state();
- std::pair<bool, std::size_t> __do_rehash
- = _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count, 1);
+ std::pair<bool, std::size_t> __do_rehash =
+ _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count, 1);
- __try
- {
if (__do_rehash.first)
_M_rehash(__do_rehash.second, __saved_state);
+ __node_type* __node = __node_accessor();
this->_M_store_code(__node, __code);
const key_type& __k = this->_M_extract()(__node->_M_v());
size_type __bkt = _M_bucket_index(__k, __code);
@@ -1779,20 +1765,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
}
else
- // The inserted node has no equivalent in the
- // hashtable. We must insert the new node at the
- // beginning of the bucket to preserve equivalent
- // elements' relative positions.
+ // The inserted node has no equivalent in the hashtable. We must
+ // insert the new node at the beginning of the bucket to preserve
+ // equivalent elements' relative positions.
_M_insert_bucket_begin(__bkt, __node);
++_M_element_count;
return iterator(__node);
}
- __catch(...)
- {
- this->_M_deallocate_node(__node);
- __throw_exception_again;
- }
- }
// Insert v if no element with its key is already present.
template<typename _Key, typename _Value,
@@ -1811,12 +1790,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__hash_code __code = this->_M_hash_code(__k);
size_type __bkt = _M_bucket_index(__k, __code);
- __node_type* __n = _M_find_node(__bkt, __k, __code);
- if (__n)
- return std::make_pair(iterator(__n), false);
+ if (__node_type* __node = _M_find_node(__bkt, __k, __code))
+ return { iterator(__node), false };
- __n = __node_gen(std::forward<_Arg>(__v));
- return { _M_insert_unique_node(__bkt, __code, __n, __n_elt), true };
+ __node_sp_t __node_sp(*this, __node_gen(std::forward<_Arg>(__v)));
+ return
+ {
+ _M_insert_unique_node(__k, __bkt, __code,
+ [&__node_sp]()
+ { return __node_sp._M_release(); },
+ __n_elt),
+ true
+ };
}
// Insert v unconditionally.
@@ -1837,9 +1822,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__hash_code __code = this->_M_hash_code(this->_M_extract()(__v));
// Second allocate new node so that we don't rehash if it throws.
- __node_type* __node = __node_gen(std::forward<_Arg>(__v));
+ __node_sp_t __node_sp(*this, __node_gen(std::forward<_Arg>(__v)));
- return _M_insert_multi_node(__hint._M_cur, __code, __node);
+ return _M_insert_multi_node(__hint._M_cur, __code,
+ [&__node_sp]()
+ { return __node_sp._M_release(); });
}
template<typename _Key, typename _Value,
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 878154ae210..ddcff0ec9d0 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -170,6 +170,40 @@ namespace __detail
__hashtable_alloc& _M_h;
};
+ // Node smart pointer to benefit from RAII.
+ template<typename _NodeAlloc>
+ struct _NodeSmartPointer
+ {
+ private:
+ using __hashtable_alloc = _Hashtable_alloc<_NodeAlloc>;
+ using __node_type = typename __hashtable_alloc::__node_type;
+
+ public:
+ _NodeSmartPointer(__hashtable_alloc& __h, __node_type* __node) noexcept
+ : _M_h(__h), _M_node(__node) { }
+
+ ~_NodeSmartPointer()
+ {
+ if (_M_node)
+ _M_h._M_deallocate_node(_M_node);
+ }
+
+ __node_type*
+ _M_get() const { return _M_node; }
+
+ __node_type*
+ _M_release() noexcept
+ {
+ __node_type* __tmp = _M_node;
+ _M_node = nullptr;
+ return __tmp;
+ }
+
+ private:
+ __hashtable_alloc& _M_h;
+ __node_type* _M_node;
+ };
+
// Auxiliary types used for all instantiations of _Hashtable nodes
// and iterators.
@@ -706,17 +740,18 @@ namespace __detail
__hashtable* __h = static_cast<__hashtable*>(this);
__hash_code __code = __h->_M_hash_code(__k);
std::size_t __bkt = __h->_M_bucket_index(__k, __code);
- __node_type* __p = __h->_M_find_node(__bkt, __k, __code);
- if (!__p)
- {
- __p = __h->_M_allocate_node(std::piecewise_construct,
- std::tuple<const key_type&>(__k),
- std::tuple<>());
- return __h->_M_insert_unique_node(__bkt, __code, __p)->second;
- }
+ if (__node_type* __node = __h->_M_find_node(__bkt, __k, __code))
+ return __node->_M_v().second;
- return __p->_M_v().second;
+ using __node_sp_t = typename __hashtable::__node_sp_t;
+ __node_sp_t __node_sp(*__h,
+ __h->_M_allocate_node(std::piecewise_construct,
+ std::tuple<const key_type&>(__k),
+ std::tuple<>()));
+ return __h->_M_insert_unique_node(__k, __bkt, __code,
+ [&__node_sp]()
+ { return __node_sp._M_release(); })->second;
}
template<typename _Key, typename _Pair, typename _Alloc, typename _Equal,
@@ -731,17 +766,20 @@ namespace __detail
__hashtable* __h = static_cast<__hashtable*>(this);
__hash_code __code = __h->_M_hash_code(__k);
std::size_t __bkt = __h->_M_bucket_index(__k, __code);
- __node_type* __p = __h->_M_find_node(__bkt, __k, __code);
- if (!__p)
- {
- __p = __h->_M_allocate_node(std::piecewise_construct,
- std::forward_as_tuple(std::move(__k)),
- std::tuple<>());
- return __h->_M_insert_unique_node(__bkt, __code, __p)->second;
- }
+ if (__node_type* __node = __h->_M_find_node(__bkt, __k, __code))
+ return __node->_M_v().second;
- return __p->_M_v().second;
+ using __node_sp_t = typename __hashtable::__node_sp_t;
+ __node_sp_t __node_sp(*__h,
+ __h->_M_allocate_node(std::piecewise_construct,
+ std::forward_as_tuple(std::move(__k)),
+ std::tuple<>()));
+ return __h->_M_insert_unique_node(
+ __h->_M_extract()(__node_sp._M_get()->_M_v()),
+ __bkt, __code,
+ [&__node_sp]()
+ { return __node_sp._M_release(); })->second;
}
template<typename _Key, typename _Pair, typename _Alloc, typename _Equal,