Merge pull request #185 from boostorg/fix/uses-allocator

This commit is contained in:
joaquintides
2023-03-06 21:38:13 +01:00
committed by GitHub
11 changed files with 412 additions and 153 deletions

View File

@@ -1129,6 +1129,14 @@ _STL_RESTORE_DEPRECATED_WARNING
*/
constexpr static float const mlf = 0.875f;
template <class T>
union uninitialized_storage
{
T t_;
uninitialized_storage(){}
~uninitialized_storage(){}
};
/* foa::table interface departs in a number of ways from that of C++ unordered
* associative containers because it's not for end-user consumption
* (boost::unordered_[flat|node]_[map|set]) wrappers complete it as
@@ -1163,11 +1171,17 @@ constexpr static float const mlf = 0.875f;
* a copyable const std::string&&. foa::table::insert is extended to accept
* both init_type and value_type references.
*
* - TypePolicy::move(element_type&) returns a temporary object for value
* transfer on rehashing, move copy/assignment, and merge. For flat map, this
* object is a std::pair<Key&&,T&&>, which is generally cheaper to move
* than std::pair<const Key,T>&& because of the constness in Key. For
* node-based tables, this is used to transfer ownership of pointer.
* - TypePolicy::construct and TypePolicy::destroy are used for the
* construction and destruction of the internal types: value_type, init_type
* and element_type.
*
* - TypePolicy::move is used to provide move semantics for the internal
* types used by the container during rehashing and emplace. These types
* are init_type, value_type and emplace_type. During insertion, a
* stack-local type will be created based on the constructibility of the
* value_type and the supplied arguments. TypePolicy::move is used here
* for transfer of ownership. Similarly, TypePolicy::move is also used
* during rehashing when elements are moved to the new table.
*
* - TypePolicy::extract returns a const reference to the key part of
* a value of type value_type, init_type, element_type or
@@ -1418,18 +1432,25 @@ public:
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)
{
/* We dispatch based on whether or not the value_type is constructible from
* an rvalue reference of the deduced emplace_type. We do this specifically
* for the case of the node-based containers. To this end, we're able to
* avoid allocating a node when a duplicate element is attempted to be
* inserted. For immovable types, we instead dispatch to the routine that
* unconditionally allocates via `type_policy::construct()`.
*/
return emplace_value(
using emplace_type=typename std::conditional<
std::is_constructible<init_type,Args...>::value,
init_type,
value_type
>::type;
using insert_type=typename std::conditional<
std::is_constructible<
value_type,
emplace_type<Args...>&&>{},
std::forward<Args>(args)...);
value_type,emplace_type>::value,
emplace_type,element_type
>::type;
uninitialized_storage<insert_type> s;
auto *p=std::addressof(s.t_);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_on_exit<insert_type> guard{al(),p};
return emplace_impl(type_policy::move(*p));
}
template<typename Key,typename... Args>
@@ -1614,15 +1635,6 @@ private:
template<typename,typename,typename,typename> friend class table;
using arrays_type=table_arrays<element_type,group_type,size_policy>;
template<typename... Args>
using emplace_type = typename std::conditional<
std::is_constructible<
init_type,Args...
>::value,
init_type,
value_type
>::type;
struct clear_on_exit
{
~clear_on_exit(){x.clear();}
@@ -1641,6 +1653,14 @@ private:
bool rollback_=false;
};
template <class T>
struct destroy_on_exit
{
Allocator &a;
T *p;
~destroy_on_exit(){type_policy::destroy(a,p);};
};
Hash& h(){return hash_base::get();}
const Hash& h()const{return hash_base::get();}
Pred& pred(){return pred_base::get();}
@@ -1904,29 +1924,6 @@ private:
#pragma warning(pop) /* C4800 */
#endif
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::true_type /* movable value_type */,Args&&... args
) {
using emplace_type_t = emplace_type<Args...>;
return emplace_impl(emplace_type_t(std::forward<Args>(args)...));
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::false_type /* immovable value_type */,Args&&... args
) {
alignas(element_type)
unsigned char buf[sizeof(element_type)];
element_type* p = reinterpret_cast<element_type*>(buf);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_element_on_exit d{this,p};
(void)d;
return emplace_impl(type_policy::move(*p));
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
{

View File

@@ -0,0 +1,60 @@
/* Copyright 2023 Christian Mazakas.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#ifndef BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
#define BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
namespace boost{
namespace unordered{
namespace detail{
namespace foa{
template<class T>
struct element_type
{
using value_type=T;
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(value_type* p_):p(p_){}
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
element_type& operator=(element_type const&)=delete;
element_type& operator=(element_type&& rhs)noexcept
{
if (this!=&rhs){
p=rhs.p;
rhs.p=nullptr;
}
return *this;
}
void swap(element_type& rhs)noexcept
{
auto tmp=p;
p=rhs.p;
rhs.p=tmp;
}
};
}
}
}
}
#endif // BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP

View File

@@ -45,20 +45,30 @@ struct node_handle_base
private:
using node_value_type=typename type_policy::value_type;
node_value_type* p_=nullptr;
element_type p_;
BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage<Allocator> a_;
protected:
node_value_type& element()noexcept
node_value_type& data()noexcept
{
BOOST_ASSERT(!empty());
return *p_;
return *(p_.p);
}
node_value_type const& element()const noexcept
node_value_type const& data()const noexcept
{
return *(p_.p);
}
element_type& element()noexcept
{
BOOST_ASSERT(!empty());
return *p_;
return p_;
}
element_type const& element()const noexcept
{
BOOST_ASSERT(!empty());
return p_;
}
Allocator& al()noexcept
@@ -73,32 +83,29 @@ struct node_handle_base
return a_.t_;
}
void emplace(node_value_type* p,Allocator a)
{
BOOST_ASSERT(empty());
p_=p;
new(&a_.t_)Allocator(a);
}
void emplace(element_type&& x,Allocator a)
{
emplace(x.p,a);
BOOST_ASSERT(empty());
auto* p=x.p;
p_.p=p;
new(&a_.t_)Allocator(a);
x.p=nullptr;
}
void reset()
{
al().~Allocator();
p_=nullptr;
a_.t_.~Allocator();
p_.p=nullptr;
}
public:
constexpr node_handle_base()noexcept{}
constexpr node_handle_base()noexcept:p_{nullptr}{}
node_handle_base(node_handle_base&& nh) noexcept
{
p_.p = nullptr;
if (!nh.empty()){
emplace(nh.p_,nh.al());
emplace(std::move(nh.p_),nh.al());
nh.reset();
}
}
@@ -110,12 +117,12 @@ struct node_handle_base
if(nh.empty()){ /* empty(), nh.empty() */
/* nothing to do */
}else{ /* empty(), !nh.empty() */
emplace(nh.p_,std::move(nh.al()));
emplace(std::move(nh.p_),std::move(nh.al()));
nh.reset();
}
}else{
if(nh.empty()){ /* !empty(), nh.empty() */
type_policy::destroy(al(),p_);
type_policy::destroy(al(),&p_);
reset();
}else{ /* !empty(), !nh.empty() */
bool const pocma=
@@ -124,12 +131,12 @@ struct node_handle_base
BOOST_ASSERT(pocma||al()==nh.al());
type_policy::destroy(al(),p_);
type_policy::destroy(al(),&p_);
if(pocma){
al()=std::move(nh.al());
}
p_=nh.p_;
p_=std::move(nh.p_);
nh.reset();
}
}
@@ -137,7 +144,7 @@ struct node_handle_base
if(empty()){ /* empty(), nh.empty() */
/* nothing to do */
}else{ /* !empty(), !nh.empty() */
type_policy::destroy(al(),p_);
type_policy::destroy(al(),&p_);
reset();
}
}
@@ -147,14 +154,14 @@ struct node_handle_base
~node_handle_base()
{
if(!empty()){
type_policy::destroy(al(),p_);
type_policy::destroy(al(),&p_);
reset();
}
}
allocator_type get_allocator()const noexcept{return al();}
explicit operator bool()const noexcept{ return !empty();}
BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_==nullptr;}
BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_.p==nullptr;}
void swap(node_handle_base& nh) noexcept(
boost::allocator_is_always_equal<Allocator>::type::value||
@@ -165,12 +172,12 @@ struct node_handle_base
if(nh.empty()) {
/* nothing to do here */
} else {
emplace(nh.p_, nh.al());
emplace(std::move(nh.p_), nh.al());
nh.reset();
}
}else{
if(nh.empty()){
nh.emplace(p_,al());
nh.emplace(std::move(p_),al());
reset();
}else{
bool const pocs=
@@ -180,7 +187,7 @@ struct node_handle_base
BOOST_ASSERT(pocs || al()==nh.al());
using std::swap;
swap(p_,nh.p_);
p_.swap(nh.p_);
if(pocs)swap(al(),nh.al());
}
}

View File

@@ -53,6 +53,11 @@ namespace boost {
return kv.first;
}
static moved_type move(init_type& x)
{
return {std::move(x.first), std::move(x.second)};
}
static moved_type move(element_type& x)
{
// TODO: we probably need to launder here
@@ -61,12 +66,23 @@ namespace boost {
}
template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args)
static void construct(A& al, init_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
}

View File

@@ -46,12 +46,12 @@ namespace boost {
static element_type&& move(element_type& x) { return std::move(x); }
template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args)
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
}

View File

@@ -11,6 +11,7 @@
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_map_fwd.hpp>
@@ -45,23 +46,7 @@ namespace boost {
using value_type = std::pair<Key const, T>;
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
struct element_type
{
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
};
using element_type=foa::element_type<value_type>;
static value_type& value_from(element_type const& x) { return *(x.p); }
@@ -77,6 +62,11 @@ namespace boost {
}
static element_type&& move(element_type& x) { return std::move(x); }
static moved_type move(init_type& x)
{
return {std::move(x.first), std::move(x.second)};
}
static moved_type move(value_type& x)
{
return {std::move(const_cast<raw_key_type&>(x.first)),
@@ -96,6 +86,18 @@ namespace boost {
construct(al, p, *copy.p);
}
template <class A, class... Args>
static void construct(A& al, init_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args)
{
@@ -106,10 +108,11 @@ namespace boost {
}
BOOST_CATCH(...)
{
boost::allocator_deallocate(al,
boost::pointer_traits<
typename boost::allocator_pointer<A>::type>::pointer_to(*p->p),
1);
using pointer_type = typename boost::allocator_pointer<A>::type;
using pointer_traits = boost::pointer_traits<pointer_type>;
boost::allocator_deallocate(
al, pointer_traits::pointer_to(*(p->p)), 1);
BOOST_RETHROW
}
BOOST_CATCH_END
@@ -118,16 +121,22 @@ namespace boost {
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
boost::allocator_deallocate(al,
boost::pointer_traits<
typename boost::allocator_pointer<A>::type>::pointer_to(*p),
1);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
{
if (p->p) {
destroy(al,p->p);
using pointer_type = typename boost::allocator_pointer<A>::type;
using pointer_traits = boost::pointer_traits<pointer_type>;
destroy(al, p->p);
boost::allocator_deallocate(
al, pointer_traits::pointer_to(*(p->p)), 1);
}
}
};
@@ -137,8 +146,7 @@ namespace boost {
: public detail::foa::node_handle_base<TypePolicy, Allocator>
{
private:
using base_type =
detail::foa::node_handle_base<TypePolicy, Allocator>;
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy;
@@ -157,13 +165,13 @@ namespace boost {
key_type& key() const
{
BOOST_ASSERT(!this->empty());
return const_cast<key_type&>(this->element().first);
return const_cast<key_type&>(this->data().first);
}
mapped_type& mapped() const
{
BOOST_ASSERT(!this->empty());
return const_cast<mapped_type&>(this->element().second);
return const_cast<mapped_type&>(this->data().second);
}
};
} // namespace detail
@@ -402,10 +410,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename map_types::element_type x;
x.p = std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
auto itp = table_.insert(std::move(nh.element()));
if (itp.second) {
nh.reset();
return {itp.first, true, node_type{}};
@@ -422,10 +427,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename map_types::element_type x;
x.p = std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
auto itp = table_.insert(std::move(nh.element()));
if (itp.second) {
nh.reset();
return itp.first;

View File

@@ -11,6 +11,7 @@
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_set_fwd.hpp>
@@ -41,23 +42,7 @@ namespace boost {
static Key const& extract(value_type const& key) { return key; }
struct element_type
{
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
};
using element_type=foa::element_type<value_type>;
static value_type& value_from(element_type const& x) { return *x.p; }
static Key const& extract(element_type const& k) { return *k.p; }
@@ -78,6 +63,12 @@ namespace boost {
x.p = nullptr;
}
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args)
{
@@ -100,16 +91,16 @@ namespace boost {
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
boost::allocator_deallocate(al,
boost::pointer_traits<
typename boost::allocator_pointer<A>::type>::pointer_to(*p),
1);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
{
if (p->p) {
destroy(al, p->p);
boost::allocator_deallocate(al,
boost::pointer_traits<typename boost::allocator_pointer<
A>::type>::pointer_to(*(p->p)),
1);
}
}
};
@@ -119,8 +110,7 @@ namespace boost {
: public detail::foa::node_handle_base<TypePolicy, Allocator>
{
private:
using base_type =
detail::foa::node_handle_base<TypePolicy, Allocator>;
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy;
@@ -137,7 +127,7 @@ namespace boost {
value_type& value() const
{
BOOST_ASSERT(!this->empty());
return const_cast<value_type&>(this->element());
return const_cast<value_type&>(this->data());
}
};
} // namespace detail
@@ -390,10 +380,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename set_types::element_type x;
x.p=std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
auto itp = table_.insert(std::move(nh.element()));
if (itp.second) {
nh.reset();
return {itp.first, true, node_type{}};
@@ -410,10 +397,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename set_types::element_type x;
x.p=std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
auto itp = table_.insert(std::move(nh.element()));
if (itp.second) {
nh.reset();
return itp.first;
@@ -478,7 +462,7 @@ namespace boost {
node_type extract(key_type const& key)
{
auto pos = find(key);
return pos!=end()?extract(pos):node_type();
return pos != end() ? extract(pos) : node_type();
}
template <class K>
@@ -489,7 +473,7 @@ namespace boost {
extract(K const& key)
{
auto pos = find(key);
return pos!=end()?extract(pos):node_type();
return pos != end() ? extract(pos) : node_type();
}
template <class H2, class P2>