Improve insert/emplace robustness to self insertion

François Dumont frs.dumont@gmail.com
Wed Jul 6 19:47:00 GMT 2016


On 05/07/2016 12:47, Jonathan Wakely wrote:
> On 04/07/16 15:55 +0100, Jonathan Wakely wrote:
>> 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.
>
> Here's a testcase which passes with libstdc++ but fails with libc++
> because libc++ doesn't make a copy when inserting a T lvalue into
> std::vector<T>:
>
> #include <vector>
> #include <memory>
> #include <cassert>
>
> struct T
> {
>  T(int v = 0) : value(v) { }
>  T(const T& t);
>  T& operator=(const T& t);
>  void make_child() { child = std::make_unique<T>(value + 10); }
>  std::unique_ptr<T> child;
>  int value;
> };
>
> T::T(const T& t) : value(t.value)
> {
>  if (t.child)
>    child.reset(new T(*t.child));
> }
>
> T& T::operator=(const T& t)
> {
>  value = t.value;
>  if (t.child)
>  {
>    if (child)
>      *child = *t.child;
>    else
>      child.reset(new T(*t.child));
>  }
>  else
>    child.reset();
>  return *this;
> }
>
> int main()
> {
>  std::vector<T> v;
>  v.reserve(3);
>  v.push_back(T(1));
>  v.back().make_child();
>  v.push_back(T(2));
>  v.back().make_child();
>
>  assert(v[1].child->value == 12);
>  assert(v[1].child->child == nullptr);
>
>  v.insert(v.begin(), *v[1].child);
>
>  assert(v[0].value == 12);
>  assert(v[0].child == nullptr);
> }
>
> The problem is that the object being inserted (*v[1].child) is not an
> element of the vector, so the optimization assumes it is unchanged by
> shuffling the existing elements. That assumption is wrong.
>
> As far as I can see, this program is perfectly valid. It's slightly
> contrived to prove a point, but it's not entirely unrealistic code.
>
>
Don't you plan to add it to the testsuite ?

On my side I rebase part of my patch to reorganize a little bit code. I 
reintroduced _M_realloc_insert which isolates the code of _M_insert_aux 
used when we need to reallocate memory. So _M_insert_aux is used only 
when insertion can be done in place. It is a nice replacement for 
_M_emplace_back_aux that have been removed. In most of vector modifiers 
we start checking if we need to reallocate or not. With this 
reorganization we don't check it several times. Moreover, as soon as we 
reallocate we know that we don't need to do any temporary copy so 
insert_vs_emplace.cc test04 has been adapted and we now have no 
situation where emplace and insert are not equivalent.

     * include/bits/stl_vector.h (push_back(const value_type&)): Forward
     to _M_realloc_insert.
     (insert(const_iterator, value_type&&)): Forward to _M_insert_rval.
     (_M_realloc_insert): Declare new function.
     (_M_emplace_back_aux): Remove definition.
     * include/bits/vector.tcc (emplace_back(_Args...)):
     Use _M_realloc_insert.
     (insert(const_iterator, const value_type&)): Likewise.
     (_M_insert_rval, _M_emplace_aux): Likewise.
     (_M_emplace_back_aux): Remove declaration.
     (_M_realloc_insert): Define.
     * testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc:
     Adjust expected results for emplacing an lvalue with reallocation.

Tested under Linux x86_64.

Ok to commit ?

François
-------------- next part --------------
A non-text attachment was scrubbed...
Name: vector_w.patch
Type: text/x-patch
Size: 8799 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20160706/fce7cfa1/attachment.bin>


More information about the Libstdc++ mailing list