How do vector elements preserve their original address after a vector std::move?
Asked Answered
T

3

7

As you can see in the output, the objects of the vector pre not only "moved" to the vector post, but also preserved their original address space in memory. What is really going on behind this move? Is this behaviour expected? Say I need to have a separate vector of pointers to these objects, is it safe to assume that after this move the objects will always have their original addresses?

Actually, I have a class containing a vector like this and the vector of pointers I mentioned as members. I have also deleted the copy ctors, and defined the move ones for the class.

#include <iostream>
#include <vector>

struct B {
    int val = 0;   
    B(int aInt) : val(aInt) {  };
};

int main() {

    std::vector<B> pre;

    pre.push_back(B(1));
    pre.push_back(B(2));
    std::cout << "pre-move:\t" << (void*)&pre.at(0) << '\n';
    std::cout << "pre-move:\t" << (void*)&pre.at(1) << '\n';

    std::vector<B> post(std::move(pre));

    std::cout << "post-move:\t" << (void*)&post.at(0) << '\n';
    std::cout << "post-move:\t" << (void*)&post.at(1) << '\n';

    return 0;
}

Output:

pre-move:   0x1d7b150 
pre-move:   0x1d7b154 <------|
post-move:  0x1d7b150        |
post-move:  0x1d7b154 <------|
Tyrothricin answered 23/12, 2018 at 15:25 Comment(0)
S
11

A vector is basically nothing more than a pointer to heap-allocated memory, the current length and the current capacity of the vector.

By "moving" a vector, all you're doing is copying those values, and resetting the values of the moved-from vector.

For the data of the vector, it's basically equivalent to

original_pointer = some_place_in_memory;
new_pointer = original_pointer;   // Copies the *value* of original_pointer
original_pointer = nullptr;

There's no need to allocate new memory and copy the data in the vector.

Swordcraft answered 23/12, 2018 at 15:34 Comment(5)
When using push_back though, to add an element to a vector, all elements' addresses do change! Is this due to the vector's design to have its elements contiguously in memory, thus it copies them to another (bigger) address space?Tyrothricin
@Tyrothricin Yes that's correct. If adding an element will make the size exceed the capacity, then the data have to be reallocated to a new and larger memory area. This is the reason that pointers and iterators to elements can become invalidated.Swordcraft
Can it be relied upon that iterators and pointers remain valid (albeit in the moved to object)?Schumann
Looking at the manual it apparently can be relied upon unless the source and destination have different allocators in which case all elements are moved individually: en.cppreference.com/w/cpp/container/vector/operator%3D It may be worth adding this information as it is part of the question.Schumann
@Galik, maybe you mean this, which refers to constructors instead. Indeed 6 and 7 mention that when a custom allocator is used, an element-wise move will take place so the pointers to the original objects will be invalid.Tyrothricin
B
1

The whole point of the move operation is to avoid copying the elements, so if they got copied(there is no such thing as truly "moving" the memory) the move would be just a copy.

Vectors are usually implemented as 3 pointers: begin,end and capacity. All point to a dynamically-allocated array. Then moving the vector is just copying those three pointers and so the array and elements just change their owner.

I think it should be safe to assume that pointers to the elements remain valid.

Blane answered 23/12, 2018 at 15:34 Comment(2)
Why would capacity be a pointer? I thought it's a size_t variable.Woolsack
@MichaelMahn It could be either. In GCC at least it is a pointer. If you think about it a pointer makes sense because pointers are iterators and it is easier to use standard algorithms on two iterators than it is on one iterator and an offset.Schumann
D
0

It will be clear, if we write semantically equal code without std::vector:

B* pre = new B[2]; // Declare std::vector<B> and allocate some space to make the following line correct

B[0] = 1; // pre.push_back(B(1));
B[1] = 2; // pre.push_back(B(2));

B* post = pre; // std::vector<B> post(std::move(pre));

Actually, vector move boils down to pointer copying without reallocation. Data which the pointer points at remains in it's place, so addresses of vector elements do not change.

In this code example after the fourth line, both pre and post point to the same data with same address.

std::vector is a wrapper for a pointer to array with some additional functionality. So after doing std::vector<B> post(std::move(pre));, post will contain a pointer with the same value which was in pre.

Diverge answered 23/12, 2018 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.