diff --git a/doc/compliance.qbk b/doc/compliance.qbk index f48881ab..771e473d 100644 --- a/doc/compliance.qbk +++ b/doc/compliance.qbk @@ -8,11 +8,12 @@ Support for move semantics is implemented using Boost.Move. If rvalue references are available it will use them, but if not it uses a close, -but imperfect emulation. On such compilers you'll need to use Boost.Move -to take advantage of using movable container elements, also note that: +but imperfect emulation. On such compilers: -* Non-copyable objects can be stored in the containers, but without support - for rvalue references the container will not be movable. +* Non-copyable objects can be stored in the containers. + They can be constructed in place using `emplace`, or if they support + Boost.Move, moved into place. +* The containers themselves are not movable. * Argument forwarding is not perfect. [endsect] diff --git a/include/boost/unordered/detail/buckets.hpp b/include/boost/unordered/detail/buckets.hpp index 65392241..bd90db4e 100644 --- a/include/boost/unordered/detail/buckets.hpp +++ b/include/boost/unordered/detail/buckets.hpp @@ -73,9 +73,9 @@ namespace boost { namespace unordered { namespace iterator_detail { typedef typename Node::value_type value_type; - l_iterator() : ptr_() {} + l_iterator() BOOST_NOEXCEPT : ptr_() {} - l_iterator(iterator x, std::size_t b, std::size_t c) + l_iterator(iterator x, std::size_t b, std::size_t c) BOOST_NOEXCEPT : ptr_(x.node_), bucket_(b), bucket_count_(c) {} value_type& operator*() const { @@ -100,11 +100,11 @@ namespace boost { namespace unordered { namespace iterator_detail { return tmp; } - bool operator==(l_iterator x) const { + bool operator==(l_iterator x) const BOOST_NOEXCEPT { return ptr_ == x.ptr_; } - bool operator!=(l_iterator x) const { + bool operator!=(l_iterator x) const BOOST_NOEXCEPT { return ptr_ != x.ptr_; } }; @@ -132,13 +132,13 @@ namespace boost { namespace unordered { namespace iterator_detail { typedef typename Node::value_type value_type; - cl_iterator() : ptr_() {} + cl_iterator() BOOST_NOEXCEPT : ptr_() {} - cl_iterator(iterator x, std::size_t b, std::size_t c) : + cl_iterator(iterator x, std::size_t b, std::size_t c) BOOST_NOEXCEPT : ptr_(x.node_), bucket_(b), bucket_count_(c) {} cl_iterator(boost::unordered::iterator_detail::l_iterator< - Node, Policy> const& x) : + Node, Policy> const& x) BOOST_NOEXCEPT : ptr_(x.ptr_), bucket_(x.bucket_), bucket_count_(x.bucket_count_) {} @@ -164,11 +164,15 @@ namespace boost { namespace unordered { namespace iterator_detail { return tmp; } - friend bool operator==(cl_iterator const& x, cl_iterator const& y) { + friend bool operator==(cl_iterator const& x, cl_iterator const& y) + BOOST_NOEXCEPT + { return x.ptr_ == y.ptr_; } - friend bool operator!=(cl_iterator const& x, cl_iterator const& y) { + friend bool operator!=(cl_iterator const& x, cl_iterator const& y) + BOOST_NOEXCEPT + { return x.ptr_ != y.ptr_; } }; @@ -204,9 +208,9 @@ namespace boost { namespace unordered { namespace iterator_detail { typedef typename Node::value_type value_type; - iterator() : node_() {} + iterator() BOOST_NOEXCEPT : node_() {} - explicit iterator(typename Node::link_pointer x) : + explicit iterator(typename Node::link_pointer x) BOOST_NOEXCEPT : node_(static_cast(x)) {} value_type& operator*() const { @@ -228,11 +232,11 @@ namespace boost { namespace unordered { namespace iterator_detail { return tmp; } - bool operator==(iterator const& x) const { + bool operator==(iterator const& x) const BOOST_NOEXCEPT { return node_ == x.node_; } - bool operator!=(iterator const& x) const { + bool operator!=(iterator const& x) const BOOST_NOEXCEPT { return node_ != x.node_; } }; @@ -266,12 +270,12 @@ namespace boost { namespace unordered { namespace iterator_detail { typedef typename Node::value_type value_type; - c_iterator() : node_() {} + c_iterator() BOOST_NOEXCEPT : node_() {} - explicit c_iterator(typename Node::link_pointer x) : + explicit c_iterator(typename Node::link_pointer x) BOOST_NOEXCEPT : node_(static_cast(x)) {} - c_iterator(iterator const& x) : node_(x.node_) {} + c_iterator(iterator const& x) BOOST_NOEXCEPT : node_(x.node_) {} value_type const& operator*() const { return node_->value(); @@ -292,11 +296,15 @@ namespace boost { namespace unordered { namespace iterator_detail { return tmp; } - friend bool operator==(c_iterator const& x, c_iterator const& y) { + friend bool operator==(c_iterator const& x, c_iterator const& y) + BOOST_NOEXCEPT + { return x.node_ == y.node_; } - friend bool operator!=(c_iterator const& x, c_iterator const& y) { + friend bool operator!=(c_iterator const& x, c_iterator const& y) + BOOST_NOEXCEPT + { return x.node_ != y.node_; } }; diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index 682e3544..e33dcea2 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -117,11 +117,13 @@ namespace unordered #if defined(BOOST_UNORDERED_USE_MOVE) unordered_map(BOOST_RV_REF(unordered_map) other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } #elif !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_map(unordered_map&& other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } @@ -142,7 +144,7 @@ namespace unordered // Destructor - ~unordered_map(); + ~unordered_map() BOOST_NOEXCEPT; // Assign @@ -598,11 +600,13 @@ namespace unordered #if defined(BOOST_UNORDERED_USE_MOVE) unordered_multimap(BOOST_RV_REF(unordered_multimap) other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } #elif !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_multimap(unordered_multimap&& other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } @@ -623,7 +627,7 @@ namespace unordered // Destructor - ~unordered_multimap(); + ~unordered_multimap() BOOST_NOEXCEPT; // Assign @@ -1057,7 +1061,7 @@ namespace unordered } template - unordered_map::~unordered_map() {} + unordered_map::~unordered_map() BOOST_NOEXCEPT {} template unordered_map::unordered_map( @@ -1390,7 +1394,7 @@ namespace unordered } template - unordered_multimap::~unordered_multimap() {} + unordered_multimap::~unordered_multimap() BOOST_NOEXCEPT {} template unordered_multimap::unordered_multimap( diff --git a/include/boost/unordered/unordered_set.hpp b/include/boost/unordered/unordered_set.hpp index 0ccf1b94..9ba5002f 100644 --- a/include/boost/unordered/unordered_set.hpp +++ b/include/boost/unordered/unordered_set.hpp @@ -115,11 +115,13 @@ namespace unordered #if defined(BOOST_UNORDERED_USE_MOVE) unordered_set(BOOST_RV_REF(unordered_set) other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } #elif !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_set(unordered_set&& other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } @@ -140,7 +142,7 @@ namespace unordered // Destructor - ~unordered_set(); + ~unordered_set() BOOST_NOEXCEPT; // Assign @@ -582,11 +584,13 @@ namespace unordered #if defined(BOOST_UNORDERED_USE_MOVE) unordered_multiset(BOOST_RV_REF(unordered_multiset) other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } #elif !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_multiset(unordered_multiset&& other) + BOOST_NOEXCEPT_IF(table::nothrow_move_constructible) : table_(other.table_, boost::unordered::detail::move_tag()) { } @@ -607,7 +611,7 @@ namespace unordered // Destructor - ~unordered_multiset(); + ~unordered_multiset() BOOST_NOEXCEPT; // Assign @@ -1032,7 +1036,7 @@ namespace unordered } template - unordered_set::~unordered_set() {} + unordered_set::~unordered_set() BOOST_NOEXCEPT {} template unordered_set::unordered_set( @@ -1316,7 +1320,7 @@ namespace unordered } template - unordered_multiset::~unordered_multiset() {} + unordered_multiset::~unordered_multiset() BOOST_NOEXCEPT {} template unordered_multiset::unordered_multiset( diff --git a/test/unordered/Jamfile.v2 b/test/unordered/Jamfile.v2 index 581e4ee0..53e0d157 100644 --- a/test/unordered/Jamfile.v2 +++ b/test/unordered/Jamfile.v2 @@ -25,6 +25,7 @@ test-suite unordered [ run minimal_allocator.cpp ] [ run compile_set.cpp ] [ run compile_map.cpp ] + [ run noexcept_tests.cpp ] [ run link_test_1.cpp link_test_2.cpp ] [ run incomplete_test.cpp ] [ run simple_tests.cpp ] diff --git a/test/unordered/noexcept_tests.cpp b/test/unordered/noexcept_tests.cpp new file mode 100644 index 00000000..a4ddfa43 --- /dev/null +++ b/test/unordered/noexcept_tests.cpp @@ -0,0 +1,125 @@ + +// Copyright 2013 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) + +#include "../helpers/prefix.hpp" +#include +#include +#include "../helpers/postfix.hpp" + +#include "../helpers/test.hpp" + +namespace noexcept_tests +{ + // Test the noexcept is set correctly for the move constructor. + + struct hash_possible_exception : boost::hash + { + hash_possible_exception(hash_possible_exception const&) {} + }; + + struct equal_to_possible_exception : std::equal_to + { + equal_to_possible_exception(equal_to_possible_exception const&) {} + }; + + UNORDERED_AUTO_TEST(test_noexcept) + { +#if !defined(BOOST_NO_CXX11_NOEXCEPT) + BOOST_TEST((boost::is_nothrow_move_constructible< + boost::unordered_set >::value)); + BOOST_TEST((boost::is_nothrow_move_constructible< + boost::unordered_multiset >::value)); + BOOST_TEST((boost::is_nothrow_move_constructible< + boost::unordered_map >::value)); + BOOST_TEST((boost::is_nothrow_move_constructible< + boost::unordered_multimap >::value)); +#endif + + BOOST_TEST((!boost::is_nothrow_move_constructible< + boost::unordered_set + >::value)); + BOOST_TEST((!boost::is_nothrow_move_constructible< + boost::unordered_multiset, + equal_to_possible_exception> + >::value)); + } + + // Test that the move constructor does actually move without throwing + // an exception when it claims to. + + struct test_exception {}; + + bool throwing_test_exception = false; + void test_throw(char const* name) { + if (throwing_test_exception) { + std::cerr << "Throw exception in: " << name << std::endl; + throw test_exception(); + } + } + + class hash_nothrow_move : boost::hash + { + BOOST_COPYABLE_AND_MOVABLE(hash_nothrow_move) + + typedef boost::hash base; + public: + hash_nothrow_move(BOOST_RV_REF(hash_nothrow_move)) + BOOST_NOEXCEPT {} + + hash_nothrow_move() { test_throw("Constructor"); } + hash_nothrow_move(hash_nothrow_move const& x) { test_throw("Copy"); } + hash_nothrow_move& operator=(hash_nothrow_move const&) + { test_throw("Assign"); return *this; } + std::size_t operator()(int x) const + { test_throw("Operator"); return static_cast(*this)(x); } + }; + + class equal_to_nothrow_move : std::equal_to + { + BOOST_COPYABLE_AND_MOVABLE(equal_to_nothrow_move) + + typedef std::equal_to base; + public: + equal_to_nothrow_move(BOOST_RV_REF(equal_to_nothrow_move)) + BOOST_NOEXCEPT {} + equal_to_nothrow_move() { test_throw("Constructor"); } + equal_to_nothrow_move(equal_to_nothrow_move const& x) + { test_throw("Copy"); } + equal_to_nothrow_move& operator=(equal_to_nothrow_move const&) + { test_throw("Assign"); return *this; } + std::size_t operator()(int x, int y) const + { test_throw("Operator"); return static_cast(*this)(x, y); } + }; + + UNORDERED_AUTO_TEST(test_no_throw_when_noexcept) + { + typedef boost::unordered_set throwing_set; + + if (boost::is_nothrow_move_constructible::value) + { + throwing_test_exception = false; + + throwing_set x1; + x1.insert(10); + x1.insert(50); + + + try { + throwing_test_exception = true; + + throwing_set x2 = boost::move(x1); + BOOST_TEST(x2.size() == 2); + BOOST_TEST(*x2.begin() == 10 || *x2.begin() == 50); + } catch(test_exception) { + BOOST_TEST(false); + } + + throwing_test_exception = false; + } + } +} + +RUN_TESTS()