On 02/07/16 08:37 +0200, François Dumont wrote:
I haven't consider in this patch your remark about using allocator to build instance so don't hesitate to commit what you want and I will rebase.
Here's what I've committed to trunk. I'm getting nervous about the smart insertion trick to avoid making a copy, I have a devious testcase in mind which will break with that change. I'll share the testcase later today.
commit 6aa6fa55a89c34c51366ed432bd942e09f691a0b Author: redi <redi@138bc75d-0d04-0410-961f-82ee72b054a4> Date: Mon Jul 4 14:52:54 2016 +0000 Add tests for inserting aliased objects into std::vector 2016-07-04 Fran??ois Dumont <fdum...@gcc.gnu.org> * testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc: New test. * testsuite/23_containers/vector/modifiers/insert/self_insert.cc: New test. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@237986 138bc75d-0d04-0410-961f-82ee72b054a4 diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc new file mode 100644 index 0000000..d452b5b --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc @@ -0,0 +1,144 @@ +// { dg-options "-std=gnu++11" } + +// Copyright (C) 2016 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +#include <vector> +#include "testsuite_hooks.h" + +bool test __attribute__((unused)) = true; + +void +test01() +{ + std::vector<std::vector<int>> vv = + { + { 2, 3 }, + { 4, 5 }, + { 0, 1 } + }; + + // Make sure emplace will imply reallocation. + VERIFY( vv.capacity() == 3 ); + + vv.emplace(vv.begin(), vv[0]); + + VERIFY( vv.size() == 4 ); + VERIFY( vv[0].size() == 2 ); + VERIFY( vv[0][0] == 2 ); + VERIFY( vv[0][1] == 3 ); +} + +void +test02() +{ + std::vector<std::vector<int>> vv = + { + { 2, 3 }, + { 4, 5 }, + { 0, 1 } + }; + + // Make sure emplace won't reallocate. + vv.reserve(4); + vv.emplace(vv.begin(), vv[0]); + + VERIFY( vv.size() == 4 ); + VERIFY( vv[0].size() == 2 ); + VERIFY( vv[0][0] == 2 ); + VERIFY( vv[0][1] == 3 ); +} + +struct A +{ + A(int i) : _i(i) + { } + + A(const A& other) : _i(other._i) + { + VERIFY( other._i >= 0 ); + } + + A(A&& other) : _i(other._i) + { + VERIFY( other._i >= 0 ); + + other._i = -1; + } + + A(std::vector<A>::iterator it) : _i(it->_i) + { + VERIFY( it->_i >= 0 ); + } + + A& operator=(const A&) = default; + A& operator=(A&& other) + { + VERIFY(other._i >= 0 ); + + _i = other._i; + other._i = -1; + return *this; + } + + int _i; +}; + +void +test03() +{ + std::vector<A> va = + { + { A(1) }, + { A(2) }, + { A(3) } + }; + + // Make sure emplace will imply reallocation. + VERIFY( va.capacity() == 3 ); + + va.emplace(va.begin(), va.begin()); + + VERIFY( va.size() == 4 ); + VERIFY( va[0]._i == 1 ); +} + +void +test04() +{ + std::vector<A> va = + { + { A(1) }, + { A(2) }, + { A(3) } + }; + + // Make sure emplace won't reallocate. + va.reserve(4); + va.emplace(va.begin(), va.begin()); + + VERIFY( va.size() == 4 ); + VERIFY( va[0]._i == 1 ); +} + +int main() +{ + test01(); + test02(); + test03(); + test04(); +} diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert/self_insert.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert/self_insert.cc new file mode 100644 index 0000000..9944cbb --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert/self_insert.cc @@ -0,0 +1,70 @@ +// { dg-options "-std=gnu++11" } + +// Copyright (C) 2016 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +#include <vector> + +#include "testsuite_hooks.h" + +bool test __attribute__((unused)) = true; + +void test01() +{ + std::vector<std::vector<int>> vv = + { + { 2, 3 }, + { 4, 5 }, + { 0, 1 } + }; + + // Make sure it doesn't reallocate during insertion. + vv.reserve(4); + + vv.insert(vv.begin(), vv[0]); + + VERIFY( vv.size() == 4 ); + VERIFY( vv[0].size() == 2 ); + VERIFY( vv[0][0] == 2 ); + VERIFY( vv[0][1] == 3 ); +} + +void test02() +{ + std::vector<std::vector<int>> vv = + { + { 2, 3 }, + { 4, 5 }, + { 0, 1 } + }; + + // Make sure we will reallocate for insertion. + VERIFY( vv.capacity() == 3 ); + + vv.insert(vv.begin(), vv[0]); + + VERIFY( vv.size() == 4 ); + VERIFY( vv[0].size() == 2 ); + VERIFY( vv[0][0] == 2 ); + VERIFY( vv[0][1] == 3 ); +} + +int main() +{ + test01(); + test02(); +} commit c1e8e16c5c977ebc08d3a76e5ae9428b693d8a8c Author: redi <redi@138bc75d-0d04-0410-961f-82ee72b054a4> Date: Mon Jul 4 14:52:46 2016 +0000 Fix std::vector's use of temporary objects * include/bits/stl_vector.h (emplace(const_iterator, _Args&&...)): Define inline. Forward to _M_emplace_aux. (insert(const_iterator, value_type&&)): Forward to _M_insert_rval. (_M_insert_rval, _M_emplace_aux): Declare new functions. (_Temporary_value): New RAII type using allocator to construct/destroy. (_S_insert_aux_assign): Remove. (_M_insert_aux): Make non-variadic. * include/bits/vector.tcc (insert(const_iterator, const value_type&)): Use _Temporary_value. (emplace(const_iterator, _Args&&...)): Remove definition. (_M_insert_rval, _M_emplace_aux): Define. (_M_insert_aux): Make non-variadic, stop using _S_insert_aux_assign. (_M_fill_insert): Use _Temporary_value. * testsuite/23_containers/vector/allocator/construction.cc: New test. * testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc: Adjust expected results for emplacing an lvalue with reallocation. * testsuite/23_containers/vector/check_construct_destroy.cc: Adjust expected results to account for construction/destruction of temporary using allocator. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@237985 138bc75d-0d04-0410-961f-82ee72b054a4 diff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h index eaafa22..8e8aa7c 100644 --- a/libstdc++-v3/include/bits/stl_vector.h +++ b/libstdc++-v3/include/bits/stl_vector.h @@ -995,7 +995,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER */ template<typename... _Args> iterator - emplace(const_iterator __position, _Args&&... __args); + emplace(const_iterator __position, _Args&&... __args) + { return _M_emplace_aux(__position, std::forward<_Args>(__args)...); } /** * @brief Inserts given value into %vector before specified iterator. @@ -1040,7 +1041,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER */ iterator insert(const_iterator __position, value_type&& __x) - { return emplace(__position, std::move(__x)); } + { return _M_insert_rval(__position, std::move(__x)); } /** * @brief Inserts an initializer_list into the %vector. @@ -1431,30 +1432,65 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER _M_shrink_to_fit(); #endif - // Called by insert(p,x) #if __cplusplus < 201103L + // Called by insert(p,x) void _M_insert_aux(iterator __position, const value_type& __x); #else - template<typename... _Args> - static void - _S_insert_aux_assign(iterator __pos, _Args&&... __args) - { *__pos = _Tp(std::forward<_Args>(__args)...); } + // A value_type object constructed with _Alloc_traits::construct() + // and destroyed with _Alloc_traits::destroy(). + struct _Temporary_value + { + template<typename... _Args> + explicit + _Temporary_value(vector* __vec, _Args&&... __args) : _M_this(__vec) + { + _Alloc_traits::construct(_M_this->_M_impl, _M_ptr(), + std::forward<_Args>(__args)...); + } - static void - _S_insert_aux_assign(iterator __pos, _Tp&& __arg) - { *__pos = std::move(__arg); } + ~_Temporary_value() + { _Alloc_traits::destroy(_M_this->_M_impl, _M_ptr()); } - template<typename... _Args> + value_type& + _M_val() { return *reinterpret_cast<_Tp*>(&__buf); } + + private: + pointer + _M_ptr() { return pointer_traits<pointer>::pointer_to(_M_val()); } + + vector* _M_this; + typename aligned_storage<sizeof(_Tp), alignof(_Tp)>::type __buf; + }; + + // Called by insert(p,x) and other functions when insertion needs to + // reallocate or move existing elements. _Arg is either _Tp& or _Tp. + template<typename _Arg> void - _M_insert_aux(iterator __position, _Args&&... __args); + _M_insert_aux(iterator __position, _Arg&& __arg); + // Either move-construct at the end, or forward to _M_insert_aux. + iterator + _M_insert_rval(const_iterator __position, value_type&& __v); + + // Called by push_back(x) and emplace_back(args) when they need to + // reallocate. template<typename... _Args> void _M_emplace_back_aux(_Args&&... __args); + + // Try to emplace at the end, otherwise forward to _M_insert_aux. + template<typename... _Args> + iterator + _M_emplace_aux(const_iterator __position, _Args&&... __args); + + // Emplacing an rvalue of the correct type can use _M_insert_rval. + iterator + _M_emplace_aux(const_iterator __position, value_type&& __v) + { return _M_insert_rval(__position, std::move(__v)); } #endif - // Called by the latter. + // Called by _M_fill_insert, _M_insert_aux etc. size_type _M_check_len(size_type __n, const char* __s) const { diff --git a/libstdc++-v3/include/bits/vector.tcc b/libstdc++-v3/include/bits/vector.tcc index 9cb5464..dd0d288 100644 --- a/libstdc++-v3/include/bits/vector.tcc +++ b/libstdc++-v3/include/bits/vector.tcc @@ -124,8 +124,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER const auto __pos = begin() + (__position - cbegin()); if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) { - _Tp __x_copy = __x; - _M_insert_aux(__pos, std::move(__x_copy)); + // __x could be an existing element of this vector, so make a + // copy of it before _M_insert_aux moves elements around. + _Temporary_value __x_copy(this, __x); + _M_insert_aux(__pos, std::move(__x_copy._M_val())); } else _M_insert_aux(__pos, __x); @@ -297,30 +299,49 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER #if __cplusplus >= 201103L template<typename _Tp, typename _Alloc> + auto + vector<_Tp, _Alloc>:: + _M_insert_rval(const_iterator __position, value_type&& __v) -> iterator + { + const auto __n = __position - cbegin(); + if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage + && __position == cend()) + { + _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, + std::move(__v)); + ++this->_M_impl._M_finish; + } + else + _M_insert_aux(begin() + __n, std::move(__v)); + return iterator(this->_M_impl._M_start + __n); + } + + template<typename _Tp, typename _Alloc> template<typename... _Args> - typename vector<_Tp, _Alloc>::iterator + auto vector<_Tp, _Alloc>:: - emplace(const_iterator __position, _Args&&... __args) + _M_emplace_aux(const_iterator __position, _Args&&... __args) + -> iterator { - const size_type __n = __position - begin(); - if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage - && __position == end()) - { - _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, - std::forward<_Args>(__args)...); - ++this->_M_impl._M_finish; - } + const auto __n = __position - cbegin(); + if (__position == cend()) + emplace_back(std::forward<_Args>(__args)...); else - _M_insert_aux(begin() + (__position - cbegin()), - std::forward<_Args>(__args)...); + { + // We need to construct a temporary because something in __args... + // could alias one of the elements of the container and so we + // need to use it before _M_insert_aux moves elements around. + _Temporary_value __tmp(this, std::forward<_Args>(__args)...); + _M_insert_aux(begin() + __n, std::move(__tmp._M_val())); + } return iterator(this->_M_impl._M_start + __n); } template<typename _Tp, typename _Alloc> - template<typename... _Args> + template<typename _Arg> void vector<_Tp, _Alloc>:: - _M_insert_aux(iterator __position, _Args&&... __args) + _M_insert_aux(iterator __position, _Arg&& __arg) #else template<typename _Tp, typename _Alloc> void @@ -343,7 +364,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER #if __cplusplus < 201103L *__position = __x_copy; #else - _S_insert_aux_assign(__position, std::forward<_Args>(__args)...); + *__position = std::forward<_Arg>(__arg); #endif } else @@ -355,14 +376,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER pointer __new_finish(__new_start); __try { - // The order of the three operations is dictated by the C++0x + // The order of the three operations is dictated by the C++11 // case, where the moves could alter a new element belonging // to the existing vector. This is an issue only for callers - // taking the element by const lvalue ref (see 23.1/13). + // taking the element by lvalue ref (see last bullet of C++11 + // [res.on.arguments]). _Alloc_traits::construct(this->_M_impl, __new_start + __elems_before, #if __cplusplus >= 201103L - std::forward<_Args>(__args)...); + std::forward<_Arg>(__arg)); #else __x); #endif @@ -455,7 +477,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER if (size_type(this->_M_impl._M_end_of_storage - this->_M_impl._M_finish) >= __n) { +#if __cplusplus < 201103L value_type __x_copy = __x; +#else + _Temporary_value __tmp(this, __x); + value_type& __x_copy = __tmp._M_val(); +#endif const size_type __elems_after = end() - __position; pointer __old_finish(this->_M_impl._M_finish); if (__elems_after > __n) diff --git a/libstdc++-v3/testsuite/23_containers/vector/allocator/construction.cc b/libstdc++-v3/testsuite/23_containers/vector/allocator/construction.cc new file mode 100644 index 0000000..8040949 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/vector/allocator/construction.cc @@ -0,0 +1,105 @@ +// Copyright (C) 2016 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++11" } +// { dg-do compile } + +#include <vector> + +struct Tag { }; + +template<typename T> + struct TaggingAllocator + { + using value_type = T; + + TaggingAllocator() = default; + + template<typename U> + TaggingAllocator(const TaggingAllocator<U>&) { } + + T* + allocate(std::size_t n) { return std::allocator<T>{}.allocate(n); } + + void + deallocate(T* p, std::size_t n) { std::allocator<T>{}.deallocate(p, n); } + + template<typename U, typename... Args> + void + construct(U* p, Args&&... args) + { ::new((void*)p) U(Tag{}, std::forward<Args>(args)...); } + + template<typename U, typename... Args> + void + destroy(U* p) + { p->~U(); } + }; + +template<typename T, typename U> + bool + operator==(const TaggingAllocator<T>&, const TaggingAllocator<U>&) + { return true; } + +template<typename T, typename U> + bool + operator!=(const TaggingAllocator<T>&, const TaggingAllocator<U>&) + { return false; } + +struct X +{ + // All constructors must be passed the Tag type. + + // DefaultInsertable into vector<X, TaggingAllocator<X>>, + X(Tag) { } + // CopyInsertable into vector<X, TaggingAllocator<X>>, + X(Tag, const X&) { } + // MoveInsertable into vector<X, TaggingAllocator<X>>, and + X(Tag, X&&) { } + + // EmplaceConstructible into vector<X, TaggingAllocator<X>> from args. + template<typename... Args> + X(Tag, Args&&...) { } + + // not DefaultConstructible, CopyConstructible or MoveConstructible. + X() = delete; + X(const X&) = delete; + X(X&&) = delete; + + // CopyAssignable. + X& operator=(const X&) { return *this; } + + // MoveAssignable. + X& operator=(X&&) { return *this; } + +private: + // Not Destructible. + ~X() { } + + // Erasable from vector<X, TaggingAllocator<X>>. + friend class TaggingAllocator<X>; +}; + +template class std::vector<X, TaggingAllocator<X>>; + +void test01() +{ + std::vector<X, TaggingAllocator<X>> v; + v.reserve(3); + v.emplace_back(); + v.emplace(v.begin()); + v.emplace(v.begin(), 1, 2, 3); +} diff --git a/libstdc++-v3/testsuite/23_containers/vector/check_construct_destroy.cc b/libstdc++-v3/testsuite/23_containers/vector/check_construct_destroy.cc index cf2f7c7..b92a152 100644 --- a/libstdc++-v3/testsuite/23_containers/vector/check_construct_destroy.cc +++ b/libstdc++-v3/testsuite/23_containers/vector/check_construct_destroy.cc @@ -49,9 +49,9 @@ int main() c.reserve(100); tracker_allocator_counter::reset(); c.insert(c.begin(), arr10[0]); - ok = check_construct_destroy("Insert element", 1, 0) && ok; + ok = check_construct_destroy("Insert element", 2, 1) && ok; } - ok = check_construct_destroy("Insert element", 1, 11) && ok; + ok = check_construct_destroy("Insert element", 2, 12) && ok; { Container c(arr10, arr10 + 10); diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc index 39a3f03..1b46124 100644 --- a/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc @@ -223,7 +223,8 @@ test03() void test04() { - const X::special expected{ 0, 3, 1, 0, 3, 0 }; + const X::special expected_ins{ 0, 3, 1, 0, 3, 0 }; + const X::special expected_emp{ 0, 4, 1, 0, 4, 0 }; X::special ins, emp; { std::vector<X> v; @@ -253,8 +254,8 @@ test04() // std::cout << "----\n"; emp = X::sp; } - VERIFY( ins == emp ); - VERIFY( ins == expected ); + VERIFY( ins == expected_ins ); + VERIFY( emp == expected_emp ); } // insert vs emplace xvalue reallocation diff --git a/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc b/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc index 5693c76..5740fe1 100644 --- a/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc +++ b/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc @@ -39,45 +39,48 @@ int main() int buckets; + // Add 1 to all counts, because the std::vector used internally by the + // hashtable creates and destroys a temporary object using the allocator. + tracker_allocator_counter::reset(); { Container c; buckets = c.bucket_count(); - ok = check_construct_destroy("empty container", buckets, 0) && ok; + ok = check_construct_destroy("empty container", buckets+1, 1) && ok; } - ok = check_construct_destroy("empty container", buckets, buckets) && ok; + ok = check_construct_destroy("empty container", buckets+1, buckets+1) && ok; tracker_allocator_counter::reset(); { Container c(arr10, arr10 + 10); - ok = check_construct_destroy("Construct from range", buckets+10, 0) && ok; + ok = check_construct_destroy("Construct from range", buckets+10+1, 1) && ok; } - ok = check_construct_destroy("Construct from range", buckets+10, buckets+10) && ok; + ok = check_construct_destroy("Construct from range", buckets+10+1, buckets+10+1) && ok; tracker_allocator_counter::reset(); { Container c(arr10, arr10 + 10); c.insert(arr10a[0]); - ok = check_construct_destroy("Insert element", buckets+11, 0) && ok; + ok = check_construct_destroy("Insert element", buckets+11+1, 1) && ok; } - ok = check_construct_destroy("Insert element", buckets+11, buckets+11) && ok; + ok = check_construct_destroy("Insert element", buckets+11+1, buckets+11+1) && ok; tracker_allocator_counter::reset(); { Container c(arr10, arr10 + 10); c.insert(arr10a, arr10a+3); - ok = check_construct_destroy("Insert short range", buckets+13, 0) && ok; + ok = check_construct_destroy("Insert short range", buckets+13+1, 1) && ok; } - ok = check_construct_destroy("Insert short range", buckets+13, buckets+13) && ok; + ok = check_construct_destroy("Insert short range", buckets+13+1, buckets+13+1) && ok; tracker_allocator_counter::reset(); { Container c(arr10, arr10 + 10); c.insert(arr10a, arr10a+10); - ok = check_construct_destroy("Insert long range", buckets+20, 0) && ok; + ok = check_construct_destroy("Insert long range", buckets+20+1, 1) && ok; } - ok = check_construct_destroy("Insert long range", buckets+20, buckets+20) && ok; + ok = check_construct_destroy("Insert long range", buckets+20+1, buckets+20+1) && ok; return ok ? 0 : 1; }