diff --git a/include/boost/unordered/detail/config.hpp b/include/boost/unordered/detail/config.hpp new file mode 100644 index 00000000..28af7c98 --- /dev/null +++ b/include/boost/unordered/detail/config.hpp @@ -0,0 +1,18 @@ + +// Copyright 2008 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) + +#if !defined(BOOST_UNORDERED_DETAIL_CONFIG_HEADER) +#define BOOST_UNORDERED_DETAIL_CONFIG_HEADER + +#include + +#if defined(BOOST_NO_SFINAE) +# define BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN +#elif defined(__GNUC__) && \ + (__GNUC__ < 3 || __GNUC__ == 3 && __GNUC_MINOR__ <= 3) +# define BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN +#endif + +#endif diff --git a/include/boost/unordered/detail/hash_table.hpp b/include/boost/unordered/detail/hash_table.hpp index 53d90044..1434edef 100644 --- a/include/boost/unordered/detail/hash_table.hpp +++ b/include/boost/unordered/detail/hash_table.hpp @@ -121,6 +121,7 @@ namespace boost { } #endif + struct move_tag {}; } } diff --git a/include/boost/unordered/detail/hash_table_impl.hpp b/include/boost/unordered/detail/hash_table_impl.hpp index 881eb6d8..ebe7b7c2 100644 --- a/include/boost/unordered/detail/hash_table_impl.hpp +++ b/include/boost/unordered/detail/hash_table_impl.hpp @@ -323,6 +323,53 @@ namespace boost { buckets_(), bucket_count_(next_prime(n)), cached_begin_bucket_(), size_(0) { + BOOST_UNORDERED_MSVC_RESET_PTR(buckets_); + create_buckets(); + } + + BOOST_UNORDERED_TABLE_DATA(BOOST_UNORDERED_TABLE_DATA const& x, size_type n) + : allocators_(x.allocators_), + buckets_(), bucket_count_(next_prime(n)), + cached_begin_bucket_(), size_(0) + { + BOOST_UNORDERED_MSVC_RESET_PTR(buckets_); + create_buckets(); + } + + BOOST_UNORDERED_TABLE_DATA(BOOST_UNORDERED_TABLE_DATA& x, move_tag) + : allocators_(x.allocators_), + buckets_(x.buckets_), bucket_count_(x.bucket_count_), + cached_begin_bucket_(x.cached_begin_bucket_), size_(x.size_) + { + unordered_detail::reset(x.buckets_); + } + + BOOST_UNORDERED_TABLE_DATA(BOOST_UNORDERED_TABLE_DATA& x, + value_allocator const& a, size_type n, move_tag) + : allocators_(a), buckets_(), bucket_count_(), + cached_begin_bucket_(), size_(0) + { + if(allocators_ == x.allocators_) { + buckets_ = x.buckets_; + bucket_count_ = x.bucket_count_; + cached_begin_bucket_ = x.cached_begin_bucket_; + size_ = x.size_; + unordered_detail::reset(x.buckets_); + } + else { + BOOST_UNORDERED_MSVC_RESET_PTR(buckets_); + bucket_count_ = next_prime(n); + create_buckets(); + } + } + + // no throw + ~BOOST_UNORDERED_TABLE_DATA() + { + delete_buckets(); + } + + void create_buckets() { // The array constructor will clean up in the event of an // exception. allocator_array_constructor @@ -341,31 +388,8 @@ namespace boost { buckets_ = constructor.release(); } - BOOST_UNORDERED_TABLE_DATA(BOOST_UNORDERED_TABLE_DATA const& x, size_type n) - : allocators_(x.allocators_), - buckets_(), bucket_count_(next_prime(n)), - cached_begin_bucket_(), size_(0) - { - // The array constructor will clean up in the event of an - // exception. - allocator_array_constructor - constructor(allocators_.bucket_alloc_); - - // Creates an extra bucket to act as a sentinel. - constructor.construct(bucket(), bucket_count_ + 1); - - cached_begin_bucket_ = constructor.get() + static_cast(bucket_count_); - - // Set up the sentinel - cached_begin_bucket_->next_ = link_ptr(cached_begin_bucket_); - - // Only release the buckets once everything is successfully - // done. - buckets_ = constructor.release(); - } - // no throw - ~BOOST_UNORDERED_TABLE_DATA() + void delete_buckets() { if(buckets_) { bucket_ptr begin = cached_begin_bucket_; @@ -400,6 +424,17 @@ namespace boost { std::swap(size_, other.size_); } + // no throw + void move(BOOST_UNORDERED_TABLE_DATA& other) + { + delete_buckets(); + buckets_ = other.buckets_; + unordered_detail::reset(other.buckets_); + bucket_count_ = other.bucket_count_; + cached_begin_bucket_ = other.cached_begin_bucket_; + size_ = other.size_; + } + // Return the bucket for a hashed value. // // no throw @@ -1114,6 +1149,36 @@ namespace boost { copy_buckets(x.data_, data_, current_functions()); } + // 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 + { + 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, + x.min_buckets_for_size(x.size()), m) // throws + { + calculate_max_load(); // no throw + + if(x.data_.buckets_) { + // This can throw, but BOOST_UNORDERED_TABLE_DATA's destructor will clean + // up. + copy_buckets(x.data_, data_, current_functions()); + } + } + // Assign // // basic exception safety, if copy_functions of reserver throws @@ -1185,6 +1250,41 @@ namespace boost { x.calculate_max_load(); } + // 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. + + 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 + + if(data_.allocators_ == x.data_.allocators_) { + data_.move(x.data_); // no throw + } + 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). + data new_this(data_, x.min_buckets_for_size(x.data_.size_)); + copy_buckets(x.data_, new_this, this->*new_func_this); + + // Start updating the data here, no throw from now on. + data_.move(new_this); + } + + // We've made it, the rest is no throw. + mlf_ = x.mlf_; + func_ = new_func_this; + calculate_max_load(); + } + private: functions const& current_functions() const diff --git a/include/boost/unordered/detail/move.hpp b/include/boost/unordered/detail/move.hpp new file mode 100644 index 00000000..2eaeafb0 --- /dev/null +++ b/include/boost/unordered/detail/move.hpp @@ -0,0 +1,228 @@ +/* + Copyright 2005-2007 Adobe Systems Incorporated + + Use, modification and distribution are subject to 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_MOVE_HEADER +#define BOOST_UNORDERED_DETAIL_MOVE_HEADER + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*************************************************************************************************/ + +namespace boost { +namespace unordered_detail { + +/*************************************************************************************************/ + +namespace move_detail { + +/*************************************************************************************************/ + +#if !defined(BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN) + +/*************************************************************************************************/ + +template +struct class_has_move_assign { + class type { + typedef T& (T::*E)(T t); + typedef char (&no_type)[1]; + typedef char (&yes_type)[2]; + template struct sfinae { typedef yes_type type; }; + template + static typename sfinae<&U::operator=>::type test(int); + template + static no_type test(...); + public: + enum {value = sizeof(test(1)) == sizeof(yes_type)}; + }; + }; + +/*************************************************************************************************/ + +template +struct has_move_assign : boost::mpl::and_, class_has_move_assign > {}; + +/*************************************************************************************************/ + +class test_can_convert_anything { }; + +/*************************************************************************************************/ + +#endif // BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN + +/*************************************************************************************************/ + +/* + REVISIT (sparent@adobe.com): This is a work around for Boost 1.34.1 and VC++ 2008 where + boost::is_convertible fails to compile. +*/ + +template +struct is_convertible : boost::mpl::or_< + boost::is_same, + boost::is_convertible +> { }; + +/*************************************************************************************************/ + +} //namespace move_detail + + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief move_from is used for move_ctors. +*/ + +template +struct move_from +{ + explicit move_from(T& x) : source(x) { } + T& source; +}; + +/*************************************************************************************************/ + +#if !defined(BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN) + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief The is_movable trait can be used to identify movable types. +*/ +template +struct is_movable : boost::mpl::and_< + boost::is_convertible, T>, + move_detail::has_move_assign, + boost::mpl::not_ > + > { }; + +/*************************************************************************************************/ + +#else // BOOST_UNORDERED_NO_HAS_MOVE_ASSIGN + +// On compilers which don't have adequate SFINAE support, treat most types as unmovable, +// unless the trait is specialized. + +template +struct is_movable : boost::mpl::false_ { }; + +#endif + +/*************************************************************************************************/ + +#if !defined(BOOST_NO_SFINAE) + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief copy_sink and move_sink are used to select between overloaded operations according to + whether type T is movable and convertible to type U. +\sa move +*/ + +template +struct copy_sink : boost::enable_if< + boost::mpl::and_< + boost::unordered_detail::move_detail::is_convertible, + boost::mpl::not_ > + >, + R + > +{ }; + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief move_sink and copy_sink are used to select between overloaded operations according to + whether type T is movable and convertible to type U. + \sa move +*/ + +template +struct move_sink : boost::enable_if< + boost::mpl::and_< + boost::unordered_detail::move_detail::is_convertible, + is_movable + >, + R + > +{ }; + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief This version of move is selected when T is_movable . It in turn calls the move +constructor. This call, with the help of the return value optimization, will cause x to be moved +instead of copied to its destination. See adobe/test/move/main.cpp for examples. + +*/ +template +T move(T& x, typename move_sink::type = 0) { return T(move_from(x)); } + +/*************************************************************************************************/ + +/*! +\ingroup move_related +\brief This version of move is selected when T is not movable . The net result will be that +x gets copied. +*/ +template +T& move(T& x, typename copy_sink::type = 0) { return x; } + +/*************************************************************************************************/ + +#else // BOOST_NO_SFINAE + +// On compilers without SFINAE, define copy_sink to always use the copy function. + +template +struct copy_sink +{ + typedef R type; +} + +// Always copy the element unless this is overloaded. + +template +T& move(T& x) { + return x; +} + +#endif // BOOST_NO_SFINAE + +} // namespace unordered_detail +} // namespace boost + +/*************************************************************************************************/ + +#endif + +/*************************************************************************************************/ diff --git a/include/boost/unordered_map.hpp b/include/boost/unordered_map.hpp index ce800aaf..d8a476b6 100644 --- a/include/boost/unordered_map.hpp +++ b/include/boost/unordered_map.hpp @@ -21,6 +21,10 @@ #include #include +#if !defined(BOOST_HAS_RVALUE_REFS) +#include +#endif + namespace boost { template other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_map& operator=(unordered_map x) + { + base.move(x.base); + return *this; + } +#endif + private: BOOST_DEDUCED_TYPENAME implementation::iterator_base const& @@ -424,6 +457,36 @@ namespace boost { } +#if defined(BOOST_HAS_RVALUE_REFS) + unordered_multimap(unordered_multimap&& other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_multimap(unordered_multimap&& other, allocator_type const& a) + : base(other.base, a, boost::unordered_detail::move_tag()) + { + } + + unordered_multimap& operator=(unordered_multimap&& x) + { + base.move(x.base); + return *this; + } +#else + unordered_multimap(boost::unordered_detail::move_from other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_multimap& operator=(unordered_multimap x) + { + base.move(x.base); + return *this; + } +#endif + + private: BOOST_DEDUCED_TYPENAME implementation::iterator_base const& diff --git a/include/boost/unordered_set.hpp b/include/boost/unordered_set.hpp index f331a978..a714395c 100644 --- a/include/boost/unordered_set.hpp +++ b/include/boost/unordered_set.hpp @@ -21,6 +21,10 @@ #include #include +#if !defined(BOOST_HAS_RVALUE_REFS) +#include +#endif + namespace boost { template other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_set& operator=(unordered_set x) + { + base.move(x.base); + return *this; + } +#endif + private: BOOST_DEDUCED_TYPENAME implementation::iterator_base const& @@ -392,6 +425,35 @@ namespace boost { } +#if defined(BOOST_HAS_RVALUE_REFS) + unordered_multiset(unordered_multiset&& other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_multiset(unordered_multiset&& other, allocator_type const& a) + : base(other.base, a, boost::unordered_detail::move_tag()) + { + } + + unordered_multiset& operator=(unordered_multiset&& x) + { + base.move(x.base); + return *this; + } +#else + unordered_multiset(boost::unordered_detail::move_from other) + : base(other.base, boost::unordered_detail::move_tag()) + { + } + + unordered_multiset& operator=(unordered_multiset x) + { + base.move(x.base); + return *this; + } +#endif + private: BOOST_DEDUCED_TYPENAME implementation::iterator_base const& diff --git a/test/unordered/Jamfile.v2 b/test/unordered/Jamfile.v2 index 7eff133a..e5db3181 100644 --- a/test/unordered/Jamfile.v2 +++ b/test/unordered/Jamfile.v2 @@ -21,6 +21,7 @@ test-suite unordered [ run equivalent_keys_tests.cpp ] [ run constructor_tests.cpp ] [ run copy_tests.cpp ] + [ run move_tests.cpp ] [ run assign_tests.cpp ] [ run insert_tests.cpp ] [ run insert_stable_tests.cpp ] diff --git a/test/unordered/move_tests.cpp b/test/unordered/move_tests.cpp new file mode 100644 index 00000000..10b3f148 --- /dev/null +++ b/test/unordered/move_tests.cpp @@ -0,0 +1,158 @@ + +// Copyright 2008 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or move at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include "../helpers/test.hpp" +#include "../objects/test.hpp" +#include "../helpers/random_values.hpp" +#include "../helpers/tracker.hpp" +#include "../helpers/equivalent.hpp" +#include "../helpers/invariants.hpp" + +namespace move_tests +{ + test::seed_t seed(98624); + + template + T empty(T* ptr) { + return T(); + } + + template + T create(test::random_values const& v, + BOOST_DEDUCED_TYPENAME T::value_type const*& first) { + T x(v.begin(), v.end()); + first = &*x.begin(); + return x; + } + + template + T create(test::random_values const& v, + BOOST_DEDUCED_TYPENAME T::value_type const*& first, + BOOST_DEDUCED_TYPENAME T::hasher hf, + BOOST_DEDUCED_TYPENAME T::key_equal eq, + BOOST_DEDUCED_TYPENAME T::allocator_type al, + float mlf) { + T x(0, hf, eq, al); + x.max_load_factor(mlf); + x.insert(v.begin(), v.end()); + first = &*x.begin(); + return x; + } + + template + void move_construct_tests1(T* ptr, test::random_generator const& generator = test::default_generator) + { + BOOST_DEDUCED_TYPENAME T::hasher hf; + BOOST_DEDUCED_TYPENAME T::key_equal eq; + BOOST_DEDUCED_TYPENAME T::allocator_type al; + + { + T y(empty(ptr)); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(y.max_load_factor() == 1.0); + test::check_equivalent_keys(y); + } + + { + test::random_values v(1000); + BOOST_DEDUCED_TYPENAME T::value_type const* first = 0; + T y(create(v, first)); + BOOST_TEST(first == &*y.begin()); + test::check_container(y, v); + test::check_equivalent_keys(y); + } + } + + template + void move_assign_tests1(T* ptr, test::random_generator const& generator = test::default_generator) + { + { + test::random_values v(500); + BOOST_DEDUCED_TYPENAME T::value_type const* first = 0; + T y; + y = create(v, first); + BOOST_TEST(first == &*y.begin()); + test::check_container(y, v); + test::check_equivalent_keys(y); + } + } + + template + void move_construct_tests2(T* ptr, + test::random_generator const& generator = test::default_generator) + { + move_construct_tests1(ptr); + + BOOST_DEDUCED_TYPENAME T::hasher hf(1); + BOOST_DEDUCED_TYPENAME T::key_equal eq(1); + BOOST_DEDUCED_TYPENAME T::allocator_type al(1); + BOOST_DEDUCED_TYPENAME T::allocator_type al2(2); + + BOOST_DEDUCED_TYPENAME T::value_type const* first; + + { + test::random_values v(500); + T y(create(v, first, hf, eq, al, 0.5)); + BOOST_TEST(first == &*y.begin()); + test::check_container(y, v); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(y.max_load_factor() == 0.5); // Not necessarily required. + test::check_equivalent_keys(y); + } + + { + // TODO: To do this correctly requires the fancy new allocator stuff. + test::random_values v(500); + T y(create(v, first, hf, eq, al, 2.0), al2); + BOOST_TEST(first != &*y.begin()); + test::check_container(y, v); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al2)); + BOOST_TEST(y.max_load_factor() == 2.0); // Not necessarily required. + test::check_equivalent_keys(y); + } + + { + test::random_values v(25); + T y(create(v, first, hf, eq, al, 1.0), al); + BOOST_TEST(first == &*y.begin()); + test::check_container(y, v); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(y.max_load_factor() == 1.0); // Not necessarily required. + test::check_equivalent_keys(y); + } + } + + boost::unordered_set >* test_set; + boost::unordered_multiset >* test_multiset; + boost::unordered_map >* test_map; + boost::unordered_multimap >* test_multimap; + + using test::default_generator; + using test::generate_collisions; + + UNORDERED_TEST(move_construct_tests1, + ((test_set)(test_multiset)(test_map)(test_multimap)) + ) + UNORDERED_TEST(move_assign_tests1, + ((test_set)(test_multiset)(test_map)(test_multimap)) + ) + UNORDERED_TEST(move_construct_tests2, + ((test_set)(test_multiset)(test_map)(test_multimap)) + ((default_generator)(generate_collisions)) + ) +} + +RUN_TESTS()