14

Assume there is a class A that contains a vector of ints. Now assume that a vector of A's is created. If a reallocation of an A object occurs (so the vector object is moved) due to a push_back for example, will pointers to the ints themselves remain valid? Is this guaranteed?

To clarify:

class A {
public:
    A() {};
    std::vector<int> a = {1,2,3,4,5,6,7,8,9};
};

int main()
{
    std::vector<A> aVec(2);

    int *x1 = &(aVec[1].a[2]);
    A *x2 = &aVec[1];
    std::cout << x1 << " - " << x2 << " - " << aVec.capacity() << "\n";

    aVec.resize(30);

    int *y1 = &(aVec[1].a[2]);
    A *y2 = &aVec[1];
    std::cout << y1 << " - " << y2 << " - " << aVec.capacity() << "\n";
}

Running this code gives this:

0x1810088 - 0x1810028 - 2
0x1810088 - 0x18100c8 - 30

so it shows that the pointers remain valid. But I want to make sure that this is guaranteed and not just a chance thing. I'm leaning towards saying that this is guaranteed since the vector's internal data is dynamically allocated, but, again, just wanted to check.

I have looked here [ Iterator invalidation rules ] but it doesn't consider this specific case (i.e. reallocation of the vector object itself).

UPDATE:

I tried this to check what I wrote in the comments for Jarod42's answer:

std::vector<std::vector<int>> aVec(2, {1,2,3});

int *x1 = &(aVec[1][2]);
std::vector<int> *x2 = &aVec[1];
std::cout << x1 << " - " << x2 << " - " << aVec.capacity() << "\n";

aVec.resize(30);

int *y1 = &(aVec[1][2]);
std::vector<int> *y2 = &aVec[1];
std::cout << y1 << " - " << y2 << " - " << aVec.capacity() << "\n";

and got this:

0x16f0098 - 0x16f0048 - 2
0x16f0098 - 0x16f00c8 - 30

which is strange to me. I expected x2==y2.

Community
  • 1
  • 1
elatalhm
  • 516
  • 3
  • 12
  • 1
    Related: [Am I guaranteed that pointers to std::vector elements are valid after the vector is moved?](http://stackoverflow.com/q/25347599) – dyp Jan 02 '15 at 02:24

3 Answers3

12

Unfortunately this is not guaranteed. That being said, it is the case that all 3 current implementations (libc++, libstdc++ and VS-2015) appear to guarantee it. The question is whether or not the move constructor for A is noexcept:

static_assert(std::is_nothrow_move_constructible<A>::value, "");

The move constructor for A is compiler supplied, and thus dependent upon the move constructor of std::vector<int>. If the move constructor of std::vector<int> is noexcept, then the move constructor for A is noexcept, else it is not.

The current draft N4296 does not mark the move constructor for vector as noexcept. However it allows implementations to do so.

This line:

aVec.resize(30);

Will use A's move constructor if that move constructor is noexcept, else it will use A's copy constructor. If it uses A's copy constructor, the location of the ints will change. If it uses A's move constructor, the location of the ints will remain stable.

libc++ and libstdc++ mark vector's move constructor as noexcept. And thus give A a noexcept move constructor.

VS-2015 says that A does not have a noexcept move constructor:

static_assert(std::is_nothrow_move_constructible<A>::value, "");

does not compile.

Nevertheless, VS-2015 does not reallocate the ints to a new address, and thus it looks like it is not conforming to the C++11 spec.

If one changes the libc++ headers such that the vector move constructor is not marked noexcept, then the ints do indeed reallocate.

Recent discussions on the committee indicate that everyone is in favor of marking the move constructor of vector noexcept (and maybe basic_string too, but not other containers). So it is possible that a future standard will guarantee the stability you seek. In the meantime, if:

static_assert(std::is_nothrow_move_constructible<A>::value, "");

compiles, then you have your guarantee, else you don't.

Update

The reason that x2 != y2 in the update is that these are the addresses of vector<int> in the vector<vector<int>>. These inner elements had to find a new (bigger) buffer to live in, just the same as if the inner element was int. But unlike int, the inner element vector<int> could move there with a move constructor (int had to copy). But whether moving or copying, the address of the inner element had to change (from the small old buffer to the big new buffer). This behavior is consistent with the original part of the question (where the inner element is also shown to change addresses).

And yes, LWG 2321 is involved, though not a contentious point. In my answer I've already assumed LWG 2321 has passed. There's really no other way for things to happen aside from overly eager debugging iterators to gratuitously (and incorrectly) invalidate themselves. Non-debugging iterators would never invalidate, and neither will pointers or references.

Wish I had the ability to easily create an animation with arrows to buffers. That would be really clear. I just don't know how to easily do that in the time I have available.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • I tried the static assert with vector (and even vector>) and it compiled. But I don't understand why the code in the updated section of the OP gave the output it gave. Shouldn't the internal data array of the vector> aVec remain in the same place and only have its ownership changed? – elatalhm Jan 02 '15 at 01:38
  • Do the 3 implementations *guarantee* it, or is it just the case that it is true? – user253751 Jan 02 '15 at 06:18
  • @immibis: The standard guarantees it if `A` is nothrow move constructible, but the standard does not guarantee that `A` *is* nothrow move constructible. libc++ and libstdc+++ guarantee that `A` is nothrow move constructible. VS-2015 guarantees that `A` *is not* nothrow move constructible, and yet incorrectly shows stability anyway. This would appear to break exception safety guarantees on VS-2015. – Howard Hinnant Jan 02 '15 at 15:02
  • @HowardHinnant could a future version of libc++ or libstdc++ or VS-2015 legally make it not nothrow move constructible? – user253751 Jan 03 '15 at 03:14
  • @immibis: Implementations can do whatever they want to. There are no standards-conforming-police (besides the free market). The current standard says that `A` is not nothrow move constructible. But it gives permission for implementations to make `A` nothrow move constructible as an extension. I do not foresee the committee removing that permission, though the future is always difficult to predict. – Howard Hinnant Jan 03 '15 at 05:19
  • For a future lunch break: here's [a Q&A at the TeX sister site](http://tex.stackexchange.com/q/19286) that shows how to quickly program such data layout diagrams using various means. And it's also [possible to make animations](http://tex.stackexchange.com/q/177057) here on SO given different pictures. Beware, it can be addictive! – TemplateRex Jan 05 '15 at 20:25
4

It would depend of your move constructor of A. but as it (*), it will use move constructor of vector<int> for a, and according to http://www.cplusplus.com/reference/vector/vector/vector/

[..] no elements are constructed (their ownership is directly transferred).

So pointers to the ints themselves remain valid.

Edit: (*) A should be noexcept for that, and std::vector is not guaranteed to be noexcept.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Ok so the reallocation calls the move constructor of A and, by default, this calls the move constructor of vector, which leaves the internal data array intact, correct? What about if it's a vector>? Will the internal data array of the vector of vectors be left in place then? – elatalhm Jan 02 '15 at 00:09
  • You understand correctly what I mean. if `a` becomes `vector>`, then `a.data()` will remain intact, and the pointers to `int` remain valid. – Jarod42 Jan 02 '15 at 00:19
3

Your A elements will be moved where possible, and A has an implicit move constructor and implicit move assignment operator, so the member vector will also be moved.

Now, moving a vector is not necessarily equivalent to a.swap(b), so you cannot rely on the implicit move functions if you want a guarantee; you could write your own.

But whether you guarantee it yourself or obtain a guarantee by looking up the code of your particular standard library implementation, you can be assured that pointers and iterators to the individual elements shall remain valid:

[C++11: 23.2.1/8]: The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange the values of a and b without invoking any move, copy, or swap operations on the individual container elements. [..]

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055