microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
9.88k stars 1.45k forks source link

Regarding `vector::emplace_back` and `vector::push_back` behaviour #4714

Closed Quick-One closed 2 weeks ago

Quick-One commented 3 weeks ago
#include <iostream>
#include <vector>
class A{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg) on " << x_arg << "\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&) on " << rhs.x << "\n"; }

private:
  int x;
};

int main (){
  std::vector<A> a;
  std::cout << "call emplace_back:\n";
  a.emplace_back (0);
  std::cout << "call emplace_back:\n";
  a.emplace_back (1);
  return 0;
}

This program gives the following output:

call emplace_back:
A (x_arg) on 0
call emplace_back:
A (x_arg) on 1
A (A &&) on 0           <- Due to copying of elements after reallocation (?)

During the second emplace_back call, the array is reallocated as the previous capacity was 1. The output seems to demonstrate that the new element is first constructed and then the existing elements are moved to the newly reallocated array. The former is a random write while the latter is a sequential write.

Wouldn't it be ideal to reverse the order of these operations to ensure all operations are sequential writes?

Similar behaviour is shown by push_back with the exception of having an extra move operation.

MagentaTreehouse commented 3 weeks ago

Looks like you are expecting existing elements to be moved before constructing the new element on reallocation. I think the current implementation has to do with making code like a.emplace_back(a[0]) work correctly. If the argument of emplace_back refers to an element in the vector, when a reallocation happens, if the existing elements are moved to the new array first (before constructing the new element), then the new element would be constructed from a moved-from object.

frederick-vs-ja commented 3 weeks ago

I think the current implementation has to do with making code like a.emplace_back(a[0]) work correctly.

Yeah. Perhaps this is required by the standard, see [sequence.reqmts]/21 Note 1.

Quick-One commented 2 weeks ago

That makes sense. That answers my question, thanks a lot.