// Copyright 2006-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) // clang-format off #include "../helpers/prefix.hpp" #include #include #include "../helpers/postfix.hpp" // clang-format on #include "../helpers/test.hpp" namespace unnecessary_copy_tests { struct count_copies { private: BOOST_COPYABLE_AND_MOVABLE(count_copies) public: static int copies; static int moves; static int id_count; count_copies() : tag_(0), id_(++id_count) { ++copies; trace_op("Default construct"); } explicit count_copies(int tag) : tag_(tag), id_(++id_count) { ++copies; trace_op("Tag construct"); } // This bizarre constructor is an attempt to confuse emplace. // // unordered_map x: // x.emplace(count_copies(1), count_copies(2)); // x.emplace(count_copies(1), count_copies(2), count_copies(3)); // // The first emplace should use the single argument constructor twice. // The second emplace should use the single argument contructor for // the key, and this constructor for the value. count_copies(count_copies const&, count_copies const& x) : tag_(x.tag_), id_(++id_count) { ++copies; trace_op("Pair construct"); } count_copies(count_copies const& x) : tag_(x.tag_), id_(++id_count) { ++copies; trace_op("Copy construct"); } count_copies(BOOST_RV_REF(count_copies) x) : tag_(x.tag_), id_(++id_count) { x.tag_ = -1; ++moves; trace_op("Move construct"); } count_copies& operator=( BOOST_COPY_ASSIGN_REF(count_copies) p) // Copy assignment { tag_ = p.tag_; ++copies; trace_op("Copy assign"); return *this; } count_copies& operator=(BOOST_RV_REF(count_copies) p) // Move assignment { tag_ = p.tag_; ++moves; trace_op("Move assign"); return *this; } ~count_copies() { trace_op("Destruct"); } void trace_op(char const* str) { BOOST_LIGHTWEIGHT_TEST_OSTREAM << str << ": " << tag_ << " (#" << id_ << ")" << std::endl; } int tag_; int id_; }; bool operator==(count_copies const& x, count_copies const& y) { return x.tag_ == y.tag_; } template T source() { return T(); } void reset() { count_copies::copies = 0; count_copies::moves = 0; BOOST_LIGHTWEIGHT_TEST_OSTREAM << "\nReset\n" << std::endl; } } #if defined(BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP) namespace boost #else namespace unnecessary_copy_tests #endif { std::size_t hash_value(unnecessary_copy_tests::count_copies const& x) { return static_cast(x.tag_); } } // Boost.Move doesn't seem to work very well on this compiler. // For example for: // // T x; // // It will default construct T, and then move it in. // For 'T const' it seems to copy. #if defined(__IBMCPP__) && __IBMCPP__ <= 1210 #define EXTRA_CONSTRUCT_COST 1 #else #define EXTRA_CONSTRUCT_COST 0 #endif #define COPY_COUNT(n) \ if (::unnecessary_copy_tests::count_copies::copies != n) { \ BOOST_ERROR("Wrong number of copies."); \ BOOST_LIGHTWEIGHT_TEST_OSTREAM \ << "Number of copies: " \ << ::unnecessary_copy_tests::count_copies::copies << " expecting: " << n \ << std::endl; \ } #define MOVE_COUNT(n) \ if (::unnecessary_copy_tests::count_copies::moves != n) { \ BOOST_ERROR("Wrong number of moves."); \ BOOST_LIGHTWEIGHT_TEST_OSTREAM \ << "Number of moves: " << ::unnecessary_copy_tests::count_copies::moves \ << " expecting: " << n << std::endl; \ } #define COPY_COUNT_RANGE(a, b) \ if (::unnecessary_copy_tests::count_copies::copies < a || \ ::unnecessary_copy_tests::count_copies::copies > b) { \ BOOST_ERROR("Wrong number of copies."); \ BOOST_LIGHTWEIGHT_TEST_OSTREAM \ << "Number of copies: " \ << ::unnecessary_copy_tests::count_copies::copies << " expecting: [" \ << a << ", " << b << "]" << std::endl; \ } #define MOVE_COUNT_RANGE(a, b) \ if (::unnecessary_copy_tests::count_copies::moves < a || \ ::unnecessary_copy_tests::count_copies::moves > b) { \ BOOST_ERROR("Wrong number of moves."); \ BOOST_LIGHTWEIGHT_TEST_OSTREAM \ << "Number of moves: " << ::unnecessary_copy_tests::count_copies::moves \ << " expecting: [" << a << ", " << b << "]" << std::endl; \ } #define COPY_COUNT_EXTRA(a, b) COPY_COUNT_RANGE(a, a + b * EXTRA_CONSTRUCT_COST) #define MOVE_COUNT_EXTRA(a, b) MOVE_COUNT_RANGE(a, a + b * EXTRA_CONSTRUCT_COST) namespace unnecessary_copy_tests { int count_copies::copies; int count_copies::moves; int count_copies::id_count; template void unnecessary_copy_insert_test(T*) { T x; BOOST_DEDUCED_TYPENAME T::value_type a; reset(); x.insert(a); COPY_COUNT(1); MOVE_COUNT(0); } template void unnecessary_copy_insert_rvalue_set_test(T*) { T x; BOOST_DEDUCED_TYPENAME T::value_type a; reset(); x.insert(boost::move(a)); COPY_COUNT(0); MOVE_COUNT(1); BOOST_DEDUCED_TYPENAME T::value_type a2; reset(); x.insert(boost::move(a)); COPY_COUNT(0); MOVE_COUNT((x.size() == 2 ? 1 : 0)); } template void unnecessary_copy_insert_rvalue_map_test(T*) { // Doesn't currently try to emulate std::pair move construction, // so std::pair's require a copy. Could try emulating it in // construct_from_args. T x; BOOST_DEDUCED_TYPENAME T::value_type a; reset(); x.insert(boost::move(a)); #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) COPY_COUNT(1); MOVE_COUNT(0); #else COPY_COUNT(0); MOVE_COUNT(1); #endif BOOST_DEDUCED_TYPENAME T::value_type a2; reset(); x.insert(boost::move(a)); #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) COPY_COUNT((x.size() == 2 ? 1 : 0)); MOVE_COUNT(0); #else COPY_COUNT(0); MOVE_COUNT((x.size() == 2 ? 1 : 0)); #endif } boost::unordered_set* set; boost::unordered_multiset* multiset; boost::unordered_map* map; boost::unordered_multimap* multimap; UNORDERED_TEST(unnecessary_copy_insert_test, ((set)(multiset)(map)(multimap))) UNORDERED_TEST(unnecessary_copy_insert_rvalue_set_test, ((set)(multiset))) UNORDERED_TEST(unnecessary_copy_insert_rvalue_map_test, ((map)(multimap))) template void unnecessary_copy_emplace_test(T*) { reset(); T x; BOOST_DEDUCED_TYPENAME T::value_type a; COPY_COUNT(1); x.emplace(a); COPY_COUNT(2); } template void unnecessary_copy_emplace_rvalue_test(T*) { reset(); T x; x.emplace(source()); #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) COPY_COUNT(1); #else COPY_COUNT(2); #endif } UNORDERED_TEST( unnecessary_copy_emplace_test, ((set)(multiset)(map)(multimap))) UNORDERED_TEST( unnecessary_copy_emplace_rvalue_test, ((set)(multiset)(map)(multimap))) #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) template void unnecessary_copy_emplace_std_move_test(T*) { reset(); T x; BOOST_DEDUCED_TYPENAME T::value_type a; COPY_COUNT(1); MOVE_COUNT(0); x.emplace(std::move(a)); COPY_COUNT(1); MOVE_COUNT(1); } UNORDERED_TEST( unnecessary_copy_emplace_std_move_test, ((set)(multiset)(map)(multimap))) #endif template void unnecessary_copy_emplace_boost_move_test(T*) { reset(); T x; BOOST_DEDUCED_TYPENAME T::value_type a; COPY_COUNT(1); MOVE_COUNT_EXTRA(0, 1); x.emplace(boost::move(a)); #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) COPY_COUNT(1); MOVE_COUNT(1); #else // Since std::pair isn't movable, move only works for sets. COPY_COUNT_RANGE(1, 2); MOVE_COUNT_RANGE(0, 1); #endif } UNORDERED_TEST( unnecessary_copy_emplace_boost_move_test, ((set)(multiset)(map)(multimap))) template void unnecessary_copy_emplace_boost_move_set_test(T*) { reset(); T x; BOOST_DEDUCED_TYPENAME T::value_type a; COPY_COUNT(1); MOVE_COUNT(0); x.emplace(boost::move(a)); COPY_COUNT(1); MOVE_COUNT(1); } UNORDERED_TEST( unnecessary_copy_emplace_boost_move_set_test, ((set)(multiset))) template void unnecessary_copy_emplace_boost_move_map_test(T*) { reset(); T x; COPY_COUNT(0); MOVE_COUNT(0); BOOST_DEDUCED_TYPENAME T::value_type a; COPY_COUNT(1); MOVE_COUNT_EXTRA(0, 1); x.emplace(boost::move(a)); #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) COPY_COUNT(2); MOVE_COUNT_EXTRA(0, 1); #else COPY_COUNT(1); MOVE_COUNT(1); #endif } UNORDERED_TEST( unnecessary_copy_emplace_boost_move_map_test, ((map)(multimap))) UNORDERED_AUTO_TEST(unnecessary_copy_emplace_set_test) { // When calling 'source' the object is moved on some compilers, but not // others. So count that here to adjust later. reset(); source(); int source_cost = ::unnecessary_copy_tests::count_copies::moves; // reset(); boost::unordered_set x; count_copies a; x.insert(a); COPY_COUNT(2); MOVE_COUNT(0); // // 0 arguments // #if !BOOST_UNORDERED_SUN_WORKAROUNDS1 // The container will have to create a copy in order to compare with // the existing element. reset(); x.emplace(); // source_cost doesn't make much sense here, but it seems to fit. COPY_COUNT(1); MOVE_COUNT(source_cost); #endif // // 1 argument // // Emplace should be able to tell that there already is an element // without creating a new one. reset(); x.emplace(a); COPY_COUNT(0); MOVE_COUNT(0); // A new object is created by source, but it shouldn't be moved or // copied. reset(); x.emplace(source()); COPY_COUNT(1); MOVE_COUNT(source_cost); // No move should take place. reset(); x.emplace(boost::move(a)); COPY_COUNT(0); MOVE_COUNT(0); // Use a new value for cases where a did get moved... count_copies b; // The container will have to create a copy in order to compare with // the existing element. reset(); x.emplace(b.tag_); COPY_COUNT(1); MOVE_COUNT(0); // // 2 arguments // // The container will have to create b copy in order to compare with // the existing element. // // Note to self: If copy_count == 0 it's an error not an optimization. // TODO: Devise a better test. reset(); x.emplace(b, b); COPY_COUNT(1); MOVE_COUNT(0); } UNORDERED_AUTO_TEST(unnecessary_copy_emplace_map_test) { // When calling 'source' the object is moved on some compilers, but not // others. So count that here to adjust later. reset(); source(); int source_cost = ::unnecessary_copy_tests::count_copies::moves; reset(); source >(); int source_pair_cost = ::unnecessary_copy_tests::count_copies::moves; // reset(); boost::unordered_map x; // TODO: Run tests for pairs without const etc. std::pair a; x.emplace(a); COPY_COUNT_EXTRA(4, 1); MOVE_COUNT_EXTRA(0, 1); // // 0 arguments // #if !BOOST_UNORDERED_SUN_WORKAROUNDS1 // COPY_COUNT(1) would be okay here. reset(); x.emplace(); #if BOOST_WORKAROUND(BOOST_MSVC, == 1700) // This is a little odd, Visual C++ 11 seems to move the pair, which // results in one copy (for the const key) and one move (for the // non-const mapped value). Since 'emplace(boost::move(a))' (see below) // has the normal result, it must be some odd consequence of how // Visual C++ 11 handles calling move for default arguments. COPY_COUNT(3); MOVE_COUNT(1); #else COPY_COUNT_EXTRA(2, 1); MOVE_COUNT_EXTRA(0, 1); #endif #endif reset(); x.emplace(boost::unordered::piecewise_construct, boost::make_tuple(), boost::make_tuple()); COPY_COUNT(2); MOVE_COUNT(0); // // 1 argument // reset(); x.emplace(a); COPY_COUNT(0); MOVE_COUNT(0); // A new object is created by source, but it shouldn't be moved or // copied. reset(); x.emplace(source >()); COPY_COUNT(2); MOVE_COUNT(source_pair_cost); #if !(defined(__GNUC__) && __cplusplus < 199900L) && \ !(defined(_MSC_VER) && _MSC_VER < 1600) count_copies part; reset(); std::pair a_ref(part, part); x.emplace(a_ref); COPY_COUNT(2); MOVE_COUNT(0); #endif // No move should take place. // (since a is already in the container) reset(); x.emplace(boost::move(a)); COPY_COUNT(0); MOVE_COUNT(0); // // 2 arguments // std::pair b; reset(); x.emplace(b.first, b.second); COPY_COUNT(0); MOVE_COUNT(0); reset(); x.emplace(source(), source()); COPY_COUNT(2); MOVE_COUNT(source_cost * 2); // source creates a single copy. reset(); x.emplace(b.first, source()); COPY_COUNT(1); MOVE_COUNT(source_cost); reset(); x.emplace(count_copies(b.first.tag_), count_copies(b.second.tag_)); COPY_COUNT(2); MOVE_COUNT(0); reset(); x.emplace(boost::unordered::piecewise_construct, boost::make_tuple(boost::ref(b.first)), boost::make_tuple(boost::ref(b.second))); COPY_COUNT(0); MOVE_COUNT(0); #if BOOST_UNORDERED_TUPLE_ARGS reset(); x.emplace(boost::unordered::piecewise_construct, std::make_tuple(std::ref(b.first)), std::make_tuple(std::ref(b.second))); COPY_COUNT(0); MOVE_COUNT(0); std::pair move_source_trial; reset(); std::make_tuple(std::move(move_source_trial.first)); std::make_tuple(std::move(move_source_trial.second)); int tuple_move_cost = ::unnecessary_copy_tests::count_copies::moves; int tuple_copy_cost = ::unnecessary_copy_tests::count_copies::copies; std::pair move_source; reset(); x.emplace(boost::unordered::piecewise_construct, std::make_tuple(std::move(move_source.first)), std::make_tuple(std::move(move_source.second))); COPY_COUNT(tuple_copy_cost); MOVE_COUNT(tuple_move_cost); #if !defined(BOOST_NO_CXX11_HDR_TUPLE) && \ !(defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ < 6) && \ !(defined(BOOST_MSVC) && BOOST_MSVC < 1700) reset(); x.emplace(boost::unordered::piecewise_construct, std::forward_as_tuple(b.first), std::forward_as_tuple(b.second)); COPY_COUNT(0); MOVE_COUNT(0); #endif #endif } } RUN_TESTS()