Validity of pointers to internal data structure when reallocating a std::vector object

后端 未结 3 1589
北荒
北荒 2021-02-19 08:25

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) du

相关标签:
3条回答
  • 2021-02-19 08:37

    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. [..]

    0 讨论(0)
  • 2021-02-19 08:52

    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.

    0 讨论(0)
  • 2021-02-19 08:58

    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.

    0 讨论(0)
提交回复
热议问题