Refactor the hash table implementation a little bit. Some of the changes are to

make implementing emplace easier.

Merged revisions 44458-44460,44463-44465 via svnmerge from 
https://svn.boost.org/svn/boost/branches/unordered/trunk

........
  r44458 | danieljames | 2008-04-16 18:31:35 +0100 (Wed, 16 Apr 2008) | 2 lines
  
  Pull out the buffered functions.
........
  r44459 | danieljames | 2008-04-16 18:31:45 +0100 (Wed, 16 Apr 2008) | 4 lines
  
  Inline construct_node and create_node into copy_group - these used to be used
  in the implementation of insert but aren't now because of insert's exception
  requirements, so keeping them around was just confusing.
........
  r44460 | danieljames | 2008-04-16 18:31:54 +0100 (Wed, 16 Apr 2008) | 4 lines
  
  Change link_node so that it takes a node_constructor containing a constructed
  node instead of a node - this makes the code a little cleaner and also
  simplifies exception safety.
........
  r44463 | danieljames | 2008-04-16 18:35:11 +0100 (Wed, 16 Apr 2008) | 2 lines
  
  Explicitly name the different insert overloads.
........
  r44464 | danieljames | 2008-04-16 18:35:22 +0100 (Wed, 16 Apr 2008) | 2 lines
  
  Explicitly name the different erase overloads.
........
  r44465 | danieljames | 2008-04-16 18:35:33 +0100 (Wed, 16 Apr 2008) | 2 lines
  
  Call the erase methods in hash_table_data directly.
........


[SVN r44489]
This commit is contained in:
Daniel James
2008-04-17 07:45:20 +00:00
parent 07addb7030
commit 15666285fa
4 changed files with 180 additions and 221 deletions

View File

@ -122,6 +122,69 @@ namespace boost {
#endif
struct move_tag {};
// Both hasher and key_equal's copy/assign can throw so double
// buffering is used to copy them.
template <typename Hash, typename Pred>
struct buffered_functions
{
typedef Hash hasher;
typedef Pred key_equal;
class functions
{
std::pair<hasher, key_equal> functions_;
public:
functions(hasher const& h, key_equal const& k)
: functions_(h, k) {}
hasher const& hash_function() const
{
return functions_.first;
}
key_equal const& key_eq() const
{
return functions_.second;
}
};
typedef functions buffered_functions::*functions_ptr;
buffered_functions(hasher const& h, key_equal const& k)
: func1_(h, k), func2_(h, k), func_(&buffered_functions::func1_) {}
// This copies the given function objects into the currently unused
// function objects and returns a pointer, that func_ can later be
// set to, to commit the change.
//
// Strong exception safety (since only usued function objects are
// changed).
functions_ptr buffer(buffered_functions const& x) {
functions_ptr ptr = func_ == &buffered_functions::func1_
? &buffered_functions::func2_ : &buffered_functions::func1_;
this->*ptr = x.current();
return ptr;
}
void set(functions_ptr ptr) {
BOOST_ASSERT(ptr != func_);
func_ = ptr;
}
functions const& current() const {
return this->*func_;
}
private:
functions func1_;
functions func2_;
functions_ptr func_; // The currently active functions.
};
}
}

View File

@ -560,8 +560,11 @@ namespace boost {
// no throw
#if BOOST_UNORDERED_EQUIVALENT_KEYS
void link_node(link_ptr n, link_ptr pos)
// If n points to the first node in a group, this adds it to the
// end of that group.
link_ptr link_node(node_constructor& a, link_ptr pos)
{
link_ptr n = a.release();
node& node_ref = get_node(n);
node& pos_ref = get_node(pos);
node_ref.next_ = pos_ref.group_prev_->next_;
@ -569,16 +572,19 @@ namespace boost {
pos_ref.group_prev_->next_ = n;
pos_ref.group_prev_ = n;
++size_;
return n;
}
void link_node_in_bucket(link_ptr n, bucket_ptr base)
link_ptr link_node_in_bucket(node_constructor& a, bucket_ptr base)
{
link_ptr n = a.release();
node& node_ref = get_node(n);
node_ref.next_ = base->next_;
node_ref.group_prev_ = n;
base->next_ = n;
++size_;
if(base < cached_begin_bucket_) cached_begin_bucket_ = base;
return n;
}
void link_group(link_ptr n, bucket_ptr base, size_type count)
@ -591,7 +597,7 @@ namespace boost {
if(base < cached_begin_bucket_) cached_begin_bucket_ = base;
}
#else
void link_node_in_bucket(link_ptr n, bucket_ptr base)
void link_node(link_ptr n, bucket_ptr base)
{
n->next_ = base->next_;
base->next_ = n;
@ -599,9 +605,16 @@ namespace boost {
if(base < cached_begin_bucket_) cached_begin_bucket_ = base;
}
link_ptr link_node_in_bucket(node_constructor& a, bucket_ptr base)
{
link_ptr n = a.release();
link_node(n, base);
return n;
}
void link_group(link_ptr n, bucket_ptr base, size_type)
{
link_node_in_bucket(n, base);
link_node(n, base);
}
#endif
@ -733,69 +746,33 @@ namespace boost {
}
#endif
// throws, strong exception-safety:
link_ptr construct_node(value_type const& v)
{
node_constructor a(allocators_);
a.construct(v);
return a.release();
}
// Create Node
// copy_group
//
// Create a node and add it to the buckets in the given position.
//
// strong exception safety.
iterator_base create_node(value_type const& v, bucket_ptr base)
{
// throws, strong exception-safety:
link_ptr n = construct_node(v);
// Rest is no throw
link_node_in_bucket(n, base);
return iterator_base(base, n);
}
#if BOOST_UNORDERED_EQUIVALENT_KEYS
iterator_base create_node(value_type const& v, iterator_base position)
{
// throws, strong exception-safety:
link_ptr n = construct_node(v);
// Rest is no throw
link_node(n, position.node_);
return iterator_base(position.bucket_, n);
}
iterator_base create_node(value_type const& v,
bucket_ptr base, link_ptr position)
{
// throws, strong exception-safety:
link_ptr n = construct_node(v);
// Rest is no throw
if(BOOST_UNORDERED_BORLAND_BOOL(position))
link_node(n, position);
else
link_node_in_bucket(n, base);
return iterator_base(base, n);
}
#endif
// Basic exception safety.
// If it throws, it only copies some of the nodes in the group.
#if BOOST_UNORDERED_EQUIVALENT_KEYS
void copy_group(link_ptr it, bucket_ptr dst)
{
node_constructor a(allocators_);
link_ptr end = next_group(it);
iterator_base pos = create_node(get_value(it), dst);
for(it = it->next_; it != end; it = it->next_)
create_node(get_value(it), pos);
a.construct(get_value(it)); // throws
link_ptr n = link_node_in_bucket(a, dst);
for(it = it->next_; it != end; it = it->next_) {
a.construct(get_value(it)); // throws
link_node(a, n);
}
}
#else
void copy_group(link_ptr it, bucket_ptr dst)
{
create_node(get_value(it), dst);
node_constructor a(allocators_);
a.construct(get_value(it)); // throws
link_node_in_bucket(a, dst);
}
#endif
@ -879,7 +856,7 @@ namespace boost {
return next;
}
iterator_base erase(iterator_base r1, iterator_base r2)
iterator_base erase_range(iterator_base r1, iterator_base r2)
{
if(r1 != r2)
{
@ -1013,36 +990,12 @@ namespace boost {
private:
class functions
{
std::pair<hasher, key_equal> functions_;
public:
functions(hasher const& h, key_equal const& k)
: functions_(h, k) {}
hasher const& hash_function() const
{
return functions_.first;
}
key_equal const& key_eq() const
{
return functions_.second;
}
};
// Both hasher and key_equal's copy/assign can throw so double
// buffering is used to copy them. func_ points to the currently
// active function objects.
typedef functions BOOST_UNORDERED_TABLE::*functions_ptr;
functions func1_;
functions func2_;
functions_ptr func_;
typedef boost::unordered_detail::buffered_functions<Hash, Pred> buffered_functions;
typedef BOOST_DEDUCED_TYPENAME buffered_functions::functions functions;
typedef BOOST_DEDUCED_TYPENAME buffered_functions::functions_ptr functions_ptr;
buffered_functions functions_;
float mlf_;
size_type max_load_;
@ -1058,9 +1011,7 @@ namespace boost {
BOOST_UNORDERED_TABLE(size_type n,
hasher const& hf, key_equal const& eq,
value_allocator const& a)
: func1_(hf, eq), // throws, cleans itself up
func2_(hf, eq), // throws, cleans itself up
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
: functions_(hf, eq), // throws, cleans itself up
mlf_(1.0f), // no throw
data_(n, a) // throws, cleans itself up
{
@ -1104,70 +1055,60 @@ namespace boost {
BOOST_UNORDERED_TABLE(I i, I j, size_type n,
hasher const& hf, key_equal const& eq,
value_allocator const& a)
: func1_(hf, eq), // throws, cleans itself up
func2_(hf, eq), // throws, cleans itself up
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
mlf_(1.0f), // no throw
data_(initial_size(i, j, n), a) // throws, cleans itself up
: functions_(hf, eq), // throws, cleans itself up
mlf_(1.0f), // no throw
data_(initial_size(i, j, n), a) // throws, cleans itself up
{
calculate_max_load(); // no throw
// This can throw, but BOOST_UNORDERED_TABLE_DATA's destructor will clean up.
insert(i, j);
insert_range(i, j);
}
// Copy Construct
BOOST_UNORDERED_TABLE(BOOST_UNORDERED_TABLE const& x)
: func1_(x.current_functions()), // throws
func2_(x.current_functions()), // throws
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
mlf_(x.mlf_), // no throw
data_(x.data_, x.min_buckets_for_size(x.size())) // throws
: functions_(x.functions_), // throws
mlf_(x.mlf_), // no throw
data_(x.data_, x.min_buckets_for_size(x.size())) // throws
{
calculate_max_load(); // no throw
// This can throw, but BOOST_UNORDERED_TABLE_DATA's destructor will clean
// up.
copy_buckets(x.data_, data_, current_functions());
copy_buckets(x.data_, data_, functions_.current());
}
// Copy Construct with allocator
BOOST_UNORDERED_TABLE(BOOST_UNORDERED_TABLE const& x,
value_allocator const& a)
: func1_(x.current_functions()), // throws
func2_(x.current_functions()), // throws
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
mlf_(x.mlf_), // no throw
: functions_(x.functions_), // throws
mlf_(x.mlf_), // no throw
data_(x.min_buckets_for_size(x.size()), a)
{
calculate_max_load(); // no throw
// This can throw, but BOOST_UNORDERED_TABLE_DATA's destructor will clean
// up.
copy_buckets(x.data_, data_, current_functions());
copy_buckets(x.data_, data_, functions_.current());
}
// Move Construct
BOOST_UNORDERED_TABLE(BOOST_UNORDERED_TABLE& x, move_tag m)
: func1_(x.current_functions()), // throws
func2_(x.current_functions()), // throws
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
mlf_(x.mlf_), // no throw
data_(x.data_, m) // throws
: functions_(x.functions_), // throws
mlf_(x.mlf_), // no throw
data_(x.data_, m) // throws
{
calculate_max_load(); // no throw
}
BOOST_UNORDERED_TABLE(BOOST_UNORDERED_TABLE& x,
value_allocator const& a, move_tag m)
: func1_(x.current_functions()), // throws
func2_(x.current_functions()), // throws
func_(&BOOST_UNORDERED_TABLE::func1_), // no throw
mlf_(x.mlf_), // no throw
data_(x.data_, a,
: functions_(x.functions_), // throws
mlf_(x.mlf_), // no throw
data_(x.data_, a,
x.min_buckets_for_size(x.size()), m) // throws
{
calculate_max_load(); // no throw
@ -1175,13 +1116,13 @@ namespace boost {
if(x.data_.buckets_) {
// This can throw, but BOOST_UNORDERED_TABLE_DATA's destructor will clean
// up.
copy_buckets(x.data_, data_, current_functions());
copy_buckets(x.data_, data_, functions_.current());
}
}
// Assign
//
// basic exception safety, if copy_functions of reserver throws
// basic exception safety, if buffered_functions::buffer or reserver throws
// the container is left in a sane, empty state. If copy_buckets
// throws the container is left with whatever was successfully
// copied.
@ -1191,11 +1132,12 @@ namespace boost {
if(this != &x)
{
data_.clear(); // no throw
func_ = copy_functions(x); // throws, strong
functions_.set(functions_.buffer(x.functions_));
// throws, strong
mlf_ = x.mlf_; // no throw
calculate_max_load(); // no throw
reserve(x.size()); // throws
copy_buckets(x.data_, data_, current_functions()); // throws
copy_buckets(x.data_, data_, functions_.current()); // throws
}
return *this;
@ -1217,10 +1159,11 @@ namespace boost {
void swap(BOOST_UNORDERED_TABLE& x)
{
// This only effects the function objects that aren't in use
// so it is strongly exception safe, via. double buffering.
functions_ptr new_func_this = copy_functions(x); // throws
functions_ptr new_func_that = x.copy_functions(*this); // throws
// These can throw, but they only affect the function objects
// that aren't in use so it is strongly exception safe, via.
// double buffering.
functions_ptr new_func_this = functions_.buffer(x.functions_);
functions_ptr new_func_that = x.functions_.buffer(functions_);
if(data_.allocators_ == x.data_.allocators_) {
data_.swap(x.data_); // no throw
@ -1230,10 +1173,10 @@ namespace boost {
// which will clean up if anything throws an exception.
// (all can throw, but with no effect as these are new objects).
data new_this(data_, x.min_buckets_for_size(x.data_.size_));
copy_buckets(x.data_, new_this, this->*new_func_this);
copy_buckets(x.data_, new_this, functions_.*new_func_this);
data new_that(x.data_, min_buckets_for_size(data_.size_));
x.copy_buckets(data_, new_that, x.*new_func_that);
x.copy_buckets(data_, new_that, x.functions_.*new_func_that);
// Start updating the data here, no throw from now on.
data_.swap(new_this);
@ -1243,8 +1186,8 @@ namespace boost {
// We've made it, the rest is no throw.
std::swap(mlf_, x.mlf_);
func_ = new_func_this;
x.func_ = new_func_that;
functions_.set(new_func_this);
x.functions_.set(new_func_that);
calculate_max_load();
x.calculate_max_load();
@ -1261,9 +1204,10 @@ namespace boost {
void move(BOOST_UNORDERED_TABLE& x)
{
// This only effects the function objects that aren't in use
// so it is strongly exception safe, via. double buffering.
functions_ptr new_func_this = copy_functions(x); // throws
// This can throw, but it only affects the function objects
// that aren't in use so it is strongly exception safe, via.
// double buffering.
functions_ptr new_func_this = functions_.buffer(x.functions_);
if(data_.allocators_ == x.data_.allocators_) {
data_.move(x.data_); // no throw
@ -1273,7 +1217,7 @@ namespace boost {
// which will clean up if anything throws an exception.
// (all can throw, but with no effect as these are new objects).
data new_this(data_, x.min_buckets_for_size(x.data_.size_));
copy_buckets(x.data_, new_this, this->*new_func_this);
copy_buckets(x.data_, new_this, functions_.*new_func_this);
// Start updating the data here, no throw from now on.
data_.move(new_this);
@ -1281,35 +1225,10 @@ namespace boost {
// We've made it, the rest is no throw.
mlf_ = x.mlf_;
func_ = new_func_this;
functions_.set(new_func_this);
calculate_max_load();
}
private:
functions const& current_functions() const
{
return this->*func_;
}
// This copies the given function objects into the currently unused
// function objects and returns a pointer, that func_ can later be
// set to, to commit the change.
//
// Strong exception safety (since only usued function objects are
// changed).
functions_ptr copy_functions(BOOST_UNORDERED_TABLE const& x)
{
// no throw:
functions_ptr ptr = func_ == &BOOST_UNORDERED_TABLE::func1_
? &BOOST_UNORDERED_TABLE::func2_ : &BOOST_UNORDERED_TABLE::func1_;
// throws, functions not in use, so strong
this->*ptr = x.current_functions();
return ptr;
}
public:
// accessors
// no throw
@ -1321,13 +1240,13 @@ namespace boost {
// no throw
hasher const& hash_function() const
{
return current_functions().hash_function();
return functions_.current().hash_function();
}
// no throw
key_equal const& key_eq() const
{
return current_functions().key_eq();
return functions_.current().key_eq();
}
// no throw
@ -1603,25 +1522,20 @@ namespace boost {
if(reserve(size() + 1))
bucket = data_.bucket_from_hash(hash_value);
// Nothing after the point can throw.
link_ptr n = a.release();
// I'm relying on link_ptr not being invalidated by
// the rehash here.
if(BOOST_UNORDERED_BORLAND_BOOL(position))
data_.link_node(n, position);
else
data_.link_node_in_bucket(n, bucket);
return iterator_base(bucket, n);
return iterator_base(bucket,
(BOOST_UNORDERED_BORLAND_BOOL(position)) ?
data_.link_node(a, position) :
data_.link_node_in_bucket(a, bucket)
);
}
// Insert (equivalent key containers)
// if hash function throws, basic exception safety
// strong otherwise
iterator_base insert(iterator_base const& it, value_type const& v)
iterator_base insert_hint(iterator_base const& it, value_type const& v)
{
// equal can throw, but with no effects
if (it == data_.end() || !equal(extract_key(v), *it)) {
@ -1649,10 +1563,8 @@ namespace boost {
// Nothing after this point can throw
link_ptr n = a.release();
data_.link_node(n, start);
return iterator_base(base, n);
return iterator_base(base,
data_.link_node(a, start));
}
}
@ -1682,9 +1594,9 @@ namespace boost {
link_ptr position = find_iterator(bucket, k);
if(BOOST_UNORDERED_BORLAND_BOOL(position))
data_.link_node(a.release(), position);
data_.link_node(a, position);
else
data_.link_node_in_bucket(a.release(), bucket);
data_.link_node_in_bucket(a, bucket);
}
}
}
@ -1705,7 +1617,7 @@ namespace boost {
// if hash function throws, or inserting > 1 element, basic exception safety
// strong otherwise
template <typename I>
void insert(I i, I j)
void insert_range(I i, I j)
{
BOOST_DEDUCED_TYPENAME boost::iterator_traversal<I>::type
iterator_traversal_tag;
@ -1742,10 +1654,7 @@ namespace boost {
// Nothing after this point can throw.
link_ptr n = a.release();
data_.link_node_in_bucket(n, bucket);
return data::get_value(n);
return data::get_value(data_.link_node_in_bucket(a, bucket));
}
}
@ -1782,8 +1691,7 @@ namespace boost {
// Nothing after this point can throw.
link_ptr n = a.release();
data_.link_node_in_bucket(n, bucket);
link_ptr n = data_.link_node_in_bucket(a, bucket);
return std::pair<iterator_base, bool>(
iterator_base(bucket, n), true);
@ -1794,7 +1702,7 @@ namespace boost {
// if hash function throws, basic exception safety
// strong otherwise
iterator_base insert(iterator_base const& it, value_type const& v)
iterator_base insert_hint(iterator_base const& it, value_type const& v)
{
if(it != data_.end() && equal(extract_key(v), *it))
return it;
@ -1827,7 +1735,7 @@ namespace boost {
// if hash function throws, or inserting > 1 element, basic exception safety
// strong otherwise
template <typename InputIterator>
void insert(InputIterator i, InputIterator j)
void insert_range(InputIterator i, InputIterator j)
{
node_constructor a(data_.allocators_);
@ -1853,23 +1761,17 @@ namespace boost {
}
// Nothing after this point can throw.
data_.link_node_in_bucket(a.release(), bucket);
data_.link_node_in_bucket(a, bucket);
}
}
}
#endif
public:
// erase
// no throw
iterator_base erase(iterator_base const& r)
{
return data_.data::erase(r);
}
// erase_key
// strong exception safety
size_type erase(key_type const& k)
size_type erase_key(key_type const& k)
{
// No side effects in initial section
bucket_ptr bucket = get_bucket(k);
@ -1879,12 +1781,6 @@ namespace boost {
return *it ? data_.erase_group(it, bucket) : 0;
}
// no throw
iterator_base erase(iterator_base const& r1, iterator_base const& r2)
{
return data_.data::erase(r1, r2);
}
// count
//
// strong exception safety, no side effects

View File

@ -207,28 +207,28 @@ namespace boost
iterator insert(const_iterator hint, const value_type& obj)
{
return iterator(base.insert(get(hint), obj));
return iterator(base.insert_hint(get(hint), obj));
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
base.insert(first, last);
base.insert_range(first, last);
}
iterator erase(const_iterator position)
{
return iterator(base.erase(get(position)));
return iterator(base.data_.erase(get(position)));
}
size_type erase(const key_type& k)
{
return base.erase(k);
return base.erase_key(k);
}
iterator erase(const_iterator first, const_iterator last)
{
return iterator(base.erase(get(first), get(last)));
return iterator(base.data_.erase_range(get(first), get(last)));
}
void clear()
@ -560,28 +560,28 @@ namespace boost
iterator insert(const_iterator hint, const value_type& obj)
{
return iterator(base.insert(get(hint), obj));
return iterator(base.insert_hint(get(hint), obj));
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
base.insert(first, last);
base.insert_range(first, last);
}
iterator erase(const_iterator position)
{
return iterator(base.erase(get(position)));
return iterator(base.data_.erase(get(position)));
}
size_type erase(const key_type& k)
{
return base.erase(k);
return base.erase_key(k);
}
iterator erase(const_iterator first, const_iterator last)
{
return iterator(base.erase(get(first), get(last)));
return iterator(base.data_.erase_range(get(first), get(last)));
}
void clear()

View File

@ -204,28 +204,28 @@ namespace boost
iterator insert(const_iterator hint, const value_type& obj)
{
return iterator(base.insert(get(hint), obj));
return iterator(base.insert_hint(get(hint), obj));
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
base.insert(first, last);
base.insert_range(first, last);
}
iterator erase(const_iterator position)
{
return iterator(base.erase(get(position)));
return iterator(base.data_.erase(get(position)));
}
size_type erase(const key_type& k)
{
return base.erase(k);
return base.erase_key(k);
}
iterator erase(const_iterator first, const_iterator last)
{
return iterator(base.erase(get(first), get(last)));
return iterator(base.data_.erase_range(get(first), get(last)));
}
void clear()
@ -527,28 +527,28 @@ namespace boost
iterator insert(const_iterator hint, const value_type& obj)
{
return iterator(base.insert(get(hint), obj));
return iterator(base.insert_hint(get(hint), obj));
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
base.insert(first, last);
base.insert_range(first, last);
}
iterator erase(const_iterator position)
{
return iterator(base.erase(get(position)));
return iterator(base.data_.erase(get(position)));
}
size_type erase(const key_type& k)
{
return base.erase(k);
return base.erase_key(k);
}
iterator erase(const_iterator first, const_iterator last)
{
return iterator(base.erase(get(first), get(last)));
return iterator(base.data_.erase_range(get(first), get(last)));
}
void clear()