Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for an old version of boost. Click here for the latest Boost documentation.

boost/unordered/detail/table.hpp


// Copyright (C) 2003-2004 Jeremy B. Maitin-Shepard.
// Copyright (C) 2005-2009 Daniel James
// 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)

#ifndef BOOST_UNORDERED_DETAIL_ALL_HPP_INCLUDED
#define BOOST_UNORDERED_DETAIL_ALL_HPP_INCLUDED

#include <cstddef>
#include <stdexcept>
#include <algorithm>
#include <boost/config/no_tr1/cmath.hpp>
#include <boost/iterator/iterator_categories.hpp>
#include <boost/throw_exception.hpp>

#include <boost/unordered/detail/buckets.hpp>

namespace boost { namespace unordered_detail {

    ////////////////////////////////////////////////////////////////////////////
    // Helper methods

    // strong exception safety, no side effects
    template <class T>
    inline bool hash_table<T>::equal(
        key_type const& k, value_type const& v) const
    {
        return this->key_eq()(k, get_key(v));
    }

    // strong exception safety, no side effects
    template <class T>
    template <class Key, class Pred>
    inline BOOST_DEDUCED_TYPENAME T::node_ptr
        hash_table<T>::find_iterator(bucket_ptr bucket, Key const& k,
            Pred const& eq) const
    {
        node_ptr it = bucket->next_;
        while (BOOST_UNORDERED_BORLAND_BOOL(it) &&
            !eq(k, get_key(node::get_value(it))))
        {
            it = node::next_group(it);
        }

        return it;
    }

    // strong exception safety, no side effects
    template <class T>
    inline BOOST_DEDUCED_TYPENAME T::node_ptr
        hash_table<T>::find_iterator(
            bucket_ptr bucket, key_type const& k) const
    {
        node_ptr it = bucket->next_;
        while (BOOST_UNORDERED_BORLAND_BOOL(it) &&
            !equal(k, node::get_value(it)))
        {
            it = node::next_group(it);
        }

        return it;
    }

    // strong exception safety, no side effects
    // pre: this->buckets_
    template <class T>
    inline BOOST_DEDUCED_TYPENAME T::node_ptr
        hash_table<T>::find_iterator(key_type const& k) const
    {
        return find_iterator(this->get_bucket(this->bucket_index(k)), k);
    }

    // strong exception safety, no side effects
    template <class T>
    inline BOOST_DEDUCED_TYPENAME T::node_ptr*
        hash_table<T>::find_for_erase(
            bucket_ptr bucket, key_type const& k) const
    {
        node_ptr* it = &bucket->next_;
        while(BOOST_UNORDERED_BORLAND_BOOL(*it) &&
            !equal(k, node::get_value(*it)))
        {
            it = &node::next_group(*it);
        }

        return it;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Load methods

    // no throw
    template <class T>
    std::size_t hash_table<T>::max_size() const
    {
        using namespace std;

        // size < mlf_ * count
        return double_to_size_t(ceil(
                (double) this->mlf_ * this->max_bucket_count())) - 1;
    }

    // strong safety
    template <class T>
    inline std::size_t hash_table<T>::bucket_index(
        key_type const& k) const
    {
        // hash_function can throw:
        return this->hash_function()(k) % this->bucket_count_;
    }


    // no throw
    template <class T>
    inline std::size_t hash_table<T>::calculate_max_load()
    {
        using namespace std;

        // From 6.3.1/13:
        // Only resize when size >= mlf_ * count
        return double_to_size_t(ceil((double) mlf_ * this->bucket_count_));
    }

    template <class T>
    void hash_table<T>::max_load_factor(float z)
    {
        BOOST_ASSERT(z > 0);
        mlf_ = (std::max)(z, minimum_max_load_factor);
        this->max_load_ = this->calculate_max_load();
    }

    // no throw
    template <class T>
    inline std::size_t hash_table<T>::min_buckets_for_size(
        std::size_t size) const
    {
        BOOST_ASSERT(this->mlf_ != 0);

        using namespace std;

        // From 6.3.1/13:
        // size < mlf_ * count
        // => count > size / mlf_
        //
        // Or from rehash post-condition:
        // count > size / mlf_
        return next_prime(double_to_size_t(floor(size / (double) mlf_)) + 1);
    }

    ////////////////////////////////////////////////////////////////////////////
    // recompute_begin_bucket

    // init_buckets

    template <class T>
    inline void hash_table<T>::init_buckets()
    {
        if (this->size_) {
            this->cached_begin_bucket_ = this->buckets_;
            while (!this->cached_begin_bucket_->next_)
                ++this->cached_begin_bucket_;
        } else {
            this->cached_begin_bucket_ = this->get_bucket(this->bucket_count_);
        }
        this->max_load_ = calculate_max_load();
    }

    // After an erase cached_begin_bucket_ might be left pointing to
    // an empty bucket, so this is called to update it
    //
    // no throw

    template <class T>
    inline void hash_table<T>::recompute_begin_bucket(bucket_ptr b)
    {
        BOOST_ASSERT(!(b < this->cached_begin_bucket_));

        if(b == this->cached_begin_bucket_)
        {
            if (this->size_ != 0) {
                while (!this->cached_begin_bucket_->next_)
                    ++this->cached_begin_bucket_;
            } else {
                this->cached_begin_bucket_ =
                    this->get_bucket(this->bucket_count_);
            }
        }
    }

    // This is called when a range has been erased
    //
    // no throw

    template <class T>
    inline void hash_table<T>::recompute_begin_bucket(
        bucket_ptr b1, bucket_ptr b2)
    {
        BOOST_ASSERT(!(b1 < this->cached_begin_bucket_) && !(b2 < b1));
        BOOST_ASSERT(BOOST_UNORDERED_BORLAND_BOOL(b2->next_));

        if(b1 == this->cached_begin_bucket_ && !b1->next_)
            this->cached_begin_bucket_ = b2;
    }

    // no throw
    template <class T>
    inline float hash_table<T>::load_factor() const
    {
        BOOST_ASSERT(this->bucket_count_ != 0);
        return static_cast<float>(this->size_)
            / static_cast<float>(this->bucket_count_);
    }

    ////////////////////////////////////////////////////////////////////////////
    // Constructors

    template <class T>
    hash_table<T>::hash_table(std::size_t num_buckets,
        hasher const& hf, key_equal const& eq, node_allocator const& a)
      : buckets(a, next_prime(num_buckets)),
        base(hf, eq),
        size_(),
        mlf_(1.0f),
        cached_begin_bucket_(),
        max_load_(0)
    {
    }

    // Copy Construct with allocator

    template <class T>
    hash_table<T>::hash_table(hash_table const& x,
        node_allocator const& a)
      : buckets(a, x.min_buckets_for_size(x.size_)),
        base(x),
        size_(x.size_),
        mlf_(x.mlf_),
        cached_begin_bucket_(),
        max_load_(0)
    {
        if(x.size_) {
            x.copy_buckets_to(*this);
            this->init_buckets();
        }
    }

    // Move Construct

    template <class T>
    hash_table<T>::hash_table(hash_table& x, move_tag)
      : buckets(x.node_alloc(), x.bucket_count_),
        base(x),
        size_(0),
        mlf_(1.0f),
        cached_begin_bucket_(),
        max_load_(0)
    {
        this->partial_swap(x);
    }

    template <class T>
    hash_table<T>::hash_table(hash_table& x,
        node_allocator const& a, move_tag)
      : buckets(a, x.bucket_count_),
        base(x),
        size_(0),
        mlf_(x.mlf_),
        cached_begin_bucket_(),
        max_load_(0)
    {
        if(a == x.node_alloc()) {
            this->partial_swap(x);
        }
        else if(x.size_) {
            x.copy_buckets_to(*this);
            this->size_ = x.size_;
            this->init_buckets();
        }
    }

    template <class T>
    hash_table<T>& hash_table<T>::operator=(
        hash_table const& x)
    {
        hash_table tmp(x, this->node_alloc());
        this->fast_swap(tmp);
        return *this;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Swap & Move
    
    // Swap
    //
    // Strong exception safety
    //
    // Can throw if hash or predicate object's copy constructor throws
    // or if allocators are unequal.

    template <class T>
    inline void hash_table<T>::partial_swap(hash_table& x)
    {
        this->buckets::swap(x); // No throw
        std::swap(this->size_, x.size_);
        std::swap(this->mlf_, x.mlf_);
        std::swap(this->cached_begin_bucket_, x.cached_begin_bucket_);
        std::swap(this->max_load_, x.max_load_);
    }

    template <class T>
    inline void hash_table<T>::fast_swap(hash_table& x)
    {
        // These can throw, but they only affect the function objects
        // that aren't in use so it is strongly exception safe, via.
        // double buffering.
        {
            set_hash_functions<hasher, key_equal> op1(*this, x);
            set_hash_functions<hasher, key_equal> op2(x, *this);
            op1.commit();
            op2.commit();
        }
        this->buckets::swap(x); // No throw
        std::swap(this->size_, x.size_);
        std::swap(this->mlf_, x.mlf_);
        std::swap(this->cached_begin_bucket_, x.cached_begin_bucket_);
        std::swap(this->max_load_, x.max_load_);
    }

    template <class T>
    inline void hash_table<T>::slow_swap(hash_table& x)
    {
        if(this == &x) return;

        {
            // These can throw, but they only affect the function objects
            // that aren't in use so it is strongly exception safe, via.
            // double buffering.
            set_hash_functions<hasher, key_equal> op1(*this, x);
            set_hash_functions<hasher, key_equal> op2(x, *this);
        
            // Create new buckets in separate hash_buckets objects
            // which will clean up if anything throws an exception.
            // (all can throw, but with no effect as these are new objects).
        
            buckets b1(this->node_alloc(), x.min_buckets_for_size(x.size_));
            if(x.size_) x.copy_buckets_to(b1);
        
            buckets b2(x.node_alloc(), this->min_buckets_for_size(this->size_));
            if(this->size_) copy_buckets_to(b2);
        
            // Modifying the data, so no throw from now on.
        
            b1.swap(*this);
            b2.swap(x);
            op1.commit();
            op2.commit();
        }
        
        std::swap(this->size_, x.size_);

        if(this->buckets_) this->init_buckets();
        if(x.buckets_) x.init_buckets();
    }

    template <class T>
    void hash_table<T>::swap(hash_table& x)
    {
        if(this->node_alloc() == x.node_alloc()) {
            if(this != &x) this->fast_swap(x);
        }
        else {
            this->slow_swap(x);
        }
    }

    
    // Move
    //
    // Strong exception safety (might change unused function objects)
    //
    // Can throw if hash or predicate object's copy constructor throws
    // or if allocators are unequal.

    template <class T>
    void hash_table<T>::move(hash_table& x)
    {
        // This can throw, but it only affects the function objects
        // that aren't in use so it is strongly exception safe, via.
        // double buffering.
        set_hash_functions<hasher, key_equal> new_func_this(*this, x);

        if(this->node_alloc() == x.node_alloc()) {
            this->buckets::move(x); // no throw
            this->size_ = x.size_;
            this->cached_begin_bucket_ = x.cached_begin_bucket_;
            this->max_load_ = x.max_load_;
            x.size_ = 0;
        }
        else {
            // Create new buckets in separate HASH_TABLE_DATA objects
            // which will clean up if anything throws an exception.
            // (all can throw, but with no effect as these are new objects).
            
            buckets b(this->node_alloc(), x.min_buckets_for_size(x.size_));
            if(x.size_) x.copy_buckets_to(b);

            // Start updating the data here, no throw from now on.
            this->size_ = x.size_;
            b.swap(*this);
            this->init_buckets();
        }

        // We've made it, the rest is no throw.
        this->mlf_ = x.mlf_;
        new_func_this.commit();
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Reserve & Rehash

    // basic exception safety
    template <class T>
    inline void hash_table<T>::create_for_insert(std::size_t size)
    {
        this->bucket_count_ = (std::max)(this->bucket_count_,
            this->min_buckets_for_size(size));
        this->create_buckets();
        this->init_buckets();
    }

    // basic exception safety
    template <class T>
    inline bool hash_table<T>::reserve_for_insert(std::size_t size)
    {
        if(size >= max_load_) {
            std::size_t num_buckets
                = this->min_buckets_for_size((std::max)(size,
                    this->size_ + (this->size_ >> 1)));
            if(num_buckets != this->bucket_count_) {
                rehash_impl(num_buckets);
                return true;
            }
        }
        
        return false;
    }

    // if hash function throws, basic exception safety
    // strong otherwise.

    template <class T>
    inline void hash_table<T>::rehash(std::size_t min_buckets)
    {
        using namespace std;

        if(!this->size_) {
            if(this->buckets_) this->delete_buckets();
            this->bucket_count_ = next_prime(min_buckets);
        }
        else {
            // no throw:
            min_buckets = next_prime((std::max)(min_buckets,
                    double_to_size_t(floor(this->size_ / (double) mlf_)) + 1));
            if(min_buckets != this->bucket_count_) rehash_impl(min_buckets);
        }
    }

    // if hash function throws, basic exception safety
    // strong otherwise

    template <class T>
    void hash_table<T>
        ::rehash_impl(std::size_t num_buckets)
    {    
        hasher const& hf = this->hash_function();
        std::size_t size = this->size_;
        bucket_ptr end = this->get_bucket(this->bucket_count_);

        buckets dst(this->node_alloc(), num_buckets);
        dst.create_buckets();

        buckets src(this->node_alloc(), this->bucket_count_);
        src.swap(*this);
        this->size_ = 0;

        for(bucket_ptr bucket = this->cached_begin_bucket_;
            bucket != end; ++bucket)
        {
            node_ptr group = bucket->next_;
            while(group) {
                // Move the first group of equivalent nodes in bucket to dst.

                // This next line throws iff the hash function throws.
                bucket_ptr dst_bucket = dst.bucket_ptr_from_hash(
                    hf(get_key_from_ptr(group)));

                node_ptr& next_group = node::next_group(group);
                bucket->next_ = next_group;
                next_group = dst_bucket->next_;
                dst_bucket->next_ = group;
                group = bucket->next_;
            }
        }

        // Swap the new nodes back into the container and setup the local
        // variables.
        this->size_ = size;
        dst.swap(*this);                        // no throw
        this->init_buckets();
    }

    ////////////////////////////////////////////////////////////////////////////
    // copy_buckets_to

    // copy_buckets_to
    //
    // basic excpetion safety. If an exception is thrown this will
    // leave dst partially filled.

    template <class T>
    void hash_table<T>
        ::copy_buckets_to(buckets& dst) const
    {
        BOOST_ASSERT(this->buckets_ && !dst.buckets_);

        hasher const& hf = this->hash_function();
        bucket_ptr end = this->get_bucket(this->bucket_count_);

        node_constructor a(dst);
        dst.create_buckets();

        // no throw:
        for(bucket_ptr i = this->cached_begin_bucket_; i != end; ++i) {
            // no throw:
            for(node_ptr it = i->next_; it;) {
                // hash function can throw.
                bucket_ptr dst_bucket = dst.bucket_ptr_from_hash(
                    hf(get_key_from_ptr(it)));
                // throws, strong

                node_ptr group_end = node::next_group(it);

                a.construct(node::get_value(it));
                node_ptr n = a.release();
                node::add_to_bucket(n, *dst_bucket);
        
                for(it = it->next_; it != group_end; it = it->next_) {
                    a.construct(node::get_value(it));
                    node::add_after_node(a.release(), n);
                }
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    // Misc. key methods

    // strong exception safety

    // count
    //
    // strong exception safety, no side effects

    template <class T>
    std::size_t hash_table<T>::count(key_type const& k) const
    {
        if(!this->size_) return 0;
        node_ptr it = find_iterator(k); // throws, strong
        return BOOST_UNORDERED_BORLAND_BOOL(it) ? node::group_count(it) : 0;
    }

    // find
    //
    // strong exception safety, no side effects
    template <class T>
    BOOST_DEDUCED_TYPENAME T::iterator_base
        hash_table<T>::find(key_type const& k) const
    {
        if(!this->size_) return this->end();

        bucket_ptr bucket = this->get_bucket(this->bucket_index(k));
        node_ptr it = find_iterator(bucket, k);

        if (BOOST_UNORDERED_BORLAND_BOOL(it))
            return iterator_base(bucket, it);
        else
            return this->end();
    }

    template <class T>
    template <class Key, class Hash, class Pred>
    BOOST_DEDUCED_TYPENAME T::iterator_base hash_table<T>::find(Key const& k,
        Hash const& h, Pred const& eq) const
    {
        if(!this->size_) return this->end();

        bucket_ptr bucket = this->get_bucket(h(k) % this->bucket_count_);
        node_ptr it = find_iterator(bucket, k, eq);

        if (BOOST_UNORDERED_BORLAND_BOOL(it))
            return iterator_base(bucket, it);
        else
            return this->end();
    }

    template <class T>
    BOOST_DEDUCED_TYPENAME T::value_type&
        hash_table<T>::at(key_type const& k) const
    {
        if(!this->size_)
            boost::throw_exception(std::out_of_range("Unable to find key in unordered_map."));

        bucket_ptr bucket = this->get_bucket(this->bucket_index(k));
        node_ptr it = find_iterator(bucket, k);

        if (!it)
            boost::throw_exception(std::out_of_range("Unable to find key in unordered_map."));

        return node::get_value(it);
    }

    // equal_range
    //
    // strong exception safety, no side effects
    template <class T>
    BOOST_DEDUCED_TYPENAME T::iterator_pair
        hash_table<T>::equal_range(key_type const& k) const
    {
        if(!this->size_)
            return iterator_pair(this->end(), this->end());

        bucket_ptr bucket = this->get_bucket(this->bucket_index(k));
        node_ptr it = find_iterator(bucket, k);
        if (BOOST_UNORDERED_BORLAND_BOOL(it)) {
            iterator_base first(iterator_base(bucket, it));
            iterator_base second(first);
            second.increment_bucket(node::next_group(second.node_));
            return iterator_pair(first, second);
        }
        else {
            return iterator_pair(this->end(), this->end());
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Erase methods    
    
    template <class T>
    void hash_table<T>::clear()
    {
        if(!this->size_) return;

        bucket_ptr end = this->get_bucket(this->bucket_count_);
        for(bucket_ptr begin = this->buckets_; begin != end; ++begin) {
            this->clear_bucket(begin);
        }

        this->size_ = 0;
        this->cached_begin_bucket_ = end;
    }

    template <class T>
    inline std::size_t hash_table<T>::erase_group(
        node_ptr* it, bucket_ptr bucket)
    {
        node_ptr pos = *it;
        node_ptr end = node::next_group(pos);
        *it = end;
        std::size_t count = this->delete_nodes(pos, end);
        this->size_ -= count;
        this->recompute_begin_bucket(bucket);
        return count;
    }
    
    template <class T>
    std::size_t hash_table<T>::erase_key(key_type const& k)
    {
        if(!this->size_) return 0;
    
        // No side effects in initial section
        bucket_ptr bucket = this->get_bucket(this->bucket_index(k));
        node_ptr* it = this->find_for_erase(bucket, k);

        // No throw.
        return *it ? this->erase_group(it, bucket) : 0;
    }

    template <class T>
    void hash_table<T>::erase(iterator_base r)
    {
        BOOST_ASSERT(r.node_);
        --this->size_;
        node::unlink_node(*r.bucket_, r.node_);
        this->delete_node(r.node_);
        // r has been invalidated but its bucket is still valid
        this->recompute_begin_bucket(r.bucket_);
    }

    template <class T>
    BOOST_DEDUCED_TYPENAME T::iterator_base
        hash_table<T>::erase_return_iterator(iterator_base r)
    {
        BOOST_ASSERT(r.node_);
        iterator_base next = r;
        next.increment();
        --this->size_;
        node::unlink_node(*r.bucket_, r.node_);
        this->delete_node(r.node_);
        // r has been invalidated but its bucket is still valid
        this->recompute_begin_bucket(r.bucket_, next.bucket_);
        return next;
    }

    template <class T>
    BOOST_DEDUCED_TYPENAME T::iterator_base
        hash_table<T>::erase_range(
            iterator_base r1, iterator_base r2)
    {
        if(r1 != r2)
        {
            BOOST_ASSERT(r1.node_);
            if (r1.bucket_ == r2.bucket_) {
                node::unlink_nodes(*r1.bucket_, r1.node_, r2.node_);
                this->size_ -= this->delete_nodes(r1.node_, r2.node_);

                // No need to call recompute_begin_bucket because
                // the nodes are only deleted from one bucket, which
                // still contains r2 after the erase.
                 BOOST_ASSERT(r1.bucket_->next_);
            }
            else {
                bucket_ptr end_bucket = r2.node_ ?
                    r2.bucket_ : this->get_bucket(this->bucket_count_);
                BOOST_ASSERT(r1.bucket_ < end_bucket);
                node::unlink_nodes(*r1.bucket_, r1.node_, node_ptr());
                this->size_ -= this->delete_nodes(r1.node_, node_ptr());

                bucket_ptr i = r1.bucket_;
                for(++i; i != end_bucket; ++i) {
                    this->size_ -= this->delete_nodes(i->next_, node_ptr());
                    i->next_ = node_ptr();
                }

                if(r2.node_) {
                    node_ptr first = r2.bucket_->next_;
                    node::unlink_nodes(*r2.bucket_, r2.node_);
                    this->size_ -= this->delete_nodes(first, r2.node_);
                }

                // r1 has been invalidated but its bucket is still
                // valid.
                this->recompute_begin_bucket(r1.bucket_, end_bucket);
            }
        }

        return r2;
    }

    template <class T>
    BOOST_DEDUCED_TYPENAME hash_table<T>::iterator_base
        hash_table<T>::emplace_empty_impl_with_node(
            node_constructor& a, std::size_t size)
    {
        key_type const& k = get_key(a.value());
        std::size_t hash_value = this->hash_function()(k);
        if(this->buckets_) this->reserve_for_insert(size);
        else this->create_for_insert(size);
        bucket_ptr bucket = this->bucket_ptr_from_hash(hash_value);
        node_ptr n = a.release();
        node::add_to_bucket(n, *bucket);
        ++this->size_;
        this->cached_begin_bucket_ = bucket;
        return iterator_base(bucket, n);
    }
}}

#endif