Why Does Reallocating a Vector Copy Instead of Moving the Elements

Why does reallocating a vector copy instead of moving the elements?

Tip-of-trunk clang + libc++ gets:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)

If you remove the noexcept from the move constructor, then you get the copy solution:

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)

Vector reallocation uses copy instead of move constructor

After tinkering with it a bit with both GCC 4.7 and 4.8, it seems that it is indeed a bug in 4.7, which only appears when the class' destructor is not marked noexcept:

struct Foo {
Foo() {}
~Foo() noexcept {}
Foo(Foo&&) noexcept { std::cout << "move constructor" << std::endl; }
Foo(const Foo&) noexcept { std::cout << "copy constructor" << std::endl; }
};

int main() {
std::vector<Foo> v;
v.reserve(2);
v.emplace_back();
v.emplace_back();
v.emplace_back();
}

GCC 4.7 displays:

move constructor
move constructor

If we remove noexcept from the destructor:

struct Foo {
Foo() {}
~Foo() {}
Foo(Foo&&) noexcept { std::cout << "move constructor" << std::endl; }
Foo(const Foo&) noexcept { std::cout << "copy constructor" << std::endl; }
};

GCC 4.7 displays:

copy constructor
copy constructor

GCC 4.8 uses the move constructor in both cases.

Why does a vector have to move its data members when reallocating

It would not be safe to merely copy the bytes. Imagine, for example, that your object has two members, p and d, and p is a pointer that points to d. If you just copy the bytes, you'd copy the value of p that points to the old location of d, which has been destroyed.

This is a simple example, but in general, the reason for C++ constructors, destructors, copy and move constructors, is to allow your object to be "smarter" than just a sequence of bytes would be. Member variables have meaning, and that meaning is understood by your code, not by the compiler.

are std::vector required to use move instead of copy?

Answer of user2079303 is good, but I summarize, just for myself.

Standard C++17 does not require std::vector to move during reallocation, at all.
It allowed to always use copy, if object is copyable.

For example, std::vector<std::vector<std::array<char, 10000>>> is allowed to copy each time, when it need to reallocate its elements on grow.

Don't put your trust on library implementers, always check how YOUR copyable + movable type work with std::vector in YOUR environment. It could be anything, because it allowed to be anything.

Why does resize() cause a copy, rather than a move, of a vector's content when capacity is exceeded?

Paragraph 23.3.6.3/14 of the C++11 Standard specifies (about the resize() member function of the vector<> class template):

Remarks: If an exception is thrown other than by the move constructor of a non-CopyInsertable T there are no effects.

In other words, this means that for X (which is CopyInsertable), resize() offers the strong guarantee: it either succeeds or leaves the state of the vector unchanged.

In order to satisfy this guarantee, implementations usally adopt the copy-and-swap idiom: if the copy constructor of X throws, we haven't altered the content of the original vector yet, so the promise is kept.

However, if the previous content of the vector were moved into the new storage rather than being copied and the move constructor threw, then we would have irreversibly changed the original content of the vector.

Therefore, implementations will use the copy constructor of X to safely transfer the content of the vector into a new storage unless the move constructor is known not to throw, in which case it is safe to move from the previous elements.

With a small change to the definition of X's move constructor (marking it as noexcept), in fact, the output of the program is now the expected one.:

struct X
{
X() { }
X(int) { }
X(X const&) { std::cout << "X(X const&)" << std::endl; }
X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
// ^^^^^^^^
};

When std::vector reallocate its memory array, is copy constructor or move constructor used?

If the move-constructor exists and is noexcept then it is used. Otherwise the copy-constructor is used.

Using a move-constructor that might throw is undesirable as it might happen that some objects are moved to the new storage and then an exception prevents the rest of the objects being moved.

The cppreference.com site does say that if the object is non-copyable , but has a non-noexcept move constructor, then it will use that move-constructor, with "unspecified behaviour" if an exception is thrown. I guess that means elements may be lost from the vector.

Benefits of reallocating memory with std::move instead of copy when vector grows?

The character data is not stored in the actual std::string object itself, but is stored elsewhere in memory that the std::string points at. So your std::vector really looks more like this instead:

       [data1a] [data2a] [data3a]
^ ^ ^
| | |
VecA:[|string1a|string2a|string3a|...]

When using copy semantics, the character data has to be copied to new memory as well, eg:

       [xxxxxx] [xxxxxx] [xxxxxx]
x x x
x x x
VecB:[|string1b|string2b|string3b|string4b|...]
| | | |
\/ \/ \/ \/
[data1b] [data2b] [data3b] [data4b]

When using move semantics instead, the new std::string objects can re-use the original pointers and not have to copy the existing data:

       [data1a] [data2a] [data3a]
^ ^ ^
| | |
VecB:[|string1b|string2b|string3b|string4b|...]
|
\/
[data4b]

C++: Automatic vector reallocation invokes copy constructors? Why?

Here's probably the simplest (but rather contrived) example:

class foo
{
int i;
int* pi; // always points to i
};

Here, the copy constructor would maintain the invariant that pi points to i. The compiler itself wouldn't be able to figure out this relationship on its own, hence the need to call the copy constructor.

does std::vector copy/move elements when re-sizing?

If it needs to relocate, something similar to std::move(xold.begin(), xold.end(), xnew.begin()); will be used. It depends on the value type and the vector usually does its own internal placement new . but it'll move if it can move.

Your move constructor

person(const person &&other) noexcept;

has a flaw though: other should not be const since it must be allowed to change other to steal its resources. In this move constructor

person(person&& other) noexcept : m_name(std::move(other.m_name)) {}

the std::strings own move constructor will do something similar to this:

string(string&& other) noexcept : 
the_size(other.the_size),
data_ptr(std::exchange(other.data_ptr, nullptr))
{}

You also need to add a move assignment operator:

person& operator=(person &&other) noexcept;


Related Topics



Leave a reply



Submit