diff --git a/include/boost/unordered/detail/hash_table.hpp b/include/boost/unordered/detail/hash_table.hpp index 1434edef..12280ea3 100644 --- a/include/boost/unordered/detail/hash_table.hpp +++ b/include/boost/unordered/detail/hash_table.hpp @@ -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 + struct buffered_functions + { + typedef Hash hasher; + typedef Pred key_equal; + + class functions + { + std::pair 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. + }; + } } diff --git a/include/boost/unordered/detail/hash_table_impl.hpp b/include/boost/unordered/detail/hash_table_impl.hpp index ebe7b7c2..6e0b918f 100644 --- a/include/boost/unordered/detail/hash_table_impl.hpp +++ b/include/boost/unordered/detail/hash_table_impl.hpp @@ -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 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 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 - void insert(I i, I j) + void insert_range(I i, I j) { BOOST_DEDUCED_TYPENAME boost::iterator_traversal::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(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 - 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 diff --git a/include/boost/unordered_map.hpp b/include/boost/unordered_map.hpp index d8a476b6..99b88a7f 100644 --- a/include/boost/unordered_map.hpp +++ b/include/boost/unordered_map.hpp @@ -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 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 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() diff --git a/include/boost/unordered_set.hpp b/include/boost/unordered_set.hpp index a714395c..5f3c5df6 100644 --- a/include/boost/unordered_set.hpp +++ b/include/boost/unordered_set.hpp @@ -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 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 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()