重新分配std :: vector对象时指向内部数据结构的指针的有效性

ela*_*lhm 13 c++ stl vector

假设有一个包含int向量的A类.现在假设创建了A的向量.如果由于push_back而发生A对象的重新分配(因此移动了矢量对象),那么指向int的指针是否仍然有效?这有保证吗?

澄清:

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";
}
Run Code Online (Sandbox Code Playgroud)

运行此代码可以得到:

0x1810088 - 0x1810028 - 2
0x1810088 - 0x18100c8 - 30
Run Code Online (Sandbox Code Playgroud)

所以它表明指针仍然有效.但我想确保这是有保证的,而不仅仅是机会.我倾向于说这是有保证的,因为向量的内部数据是动态分配的,但是,再次,只是想检查.

我看过这里[ 迭代器失效规则 ],但它没有考虑这种特殊情况(即矢量对象本身的重新分配).

更新:

我试过这个来检查我在Jarod42答案的评论中写的内容:

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";
Run Code Online (Sandbox Code Playgroud)

得到了这个:

0x16f0098 - 0x16f0048 - 2
0x16f0098 - 0x16f00c8 - 30
Run Code Online (Sandbox Code Playgroud)

这对我来说很奇怪.我期望x2 == y2.

How*_*ant 11

不幸的是,这不能保证.话虽如此,所有3个当前实现(libc ++,libstdc ++和VS-2015)似乎都能保证它.问题是移动构造函数是否A为noexcept:

static_assert(std::is_nothrow_move_constructible<A>::value, "");
Run Code Online (Sandbox Code Playgroud)

移动构造函数A是由编译器提供的,因此依赖于移动构造函数std::vector<int>.如果移动构造函数std::vector<int>是noexcept,那么移动构造函数A是noexcept,否则它不是.

当前草案N4296并未将移动构造函数标记vector为noexcept.但是它允许实现这样做.

这一行:

aVec.resize(30);
Run Code Online (Sandbox Code Playgroud)

将使用A的移动构造函数如果移动构造函数是noexcept,否则将使用A的拷贝构造函数.如果它使用A的是复制构造函数,则int的位置将会改变.如果它使用A移动构造函数,则int的位置将保持稳定.

libc ++和libstdc ++标记vector的移动构造函数为noexcept.从而给出A一个noexcept移动构造函数.

VS-2015说A没有noexcept移动构造函数:

static_assert(std::is_nothrow_move_constructible<A>::value, "");
Run Code Online (Sandbox Code Playgroud)

不编译.

尽管如此,VS-2015并没有将int重新分配给新地址,因此看起来它不符合C++ 11规范.

如果更改了libc ++头文件,使得vector移动构造函数未标记为noexcept,则int确实会重新分配.

最近关于委员会的讨论表明,每个人都赞成标记vectornoexcept 的移动构造函数(可能basic_string也是,但不是其他容器).因此,未来的标准有可能保证您寻求的稳定性.与此同时,如果:

static_assert(std::is_nothrow_move_constructible<A>::value, "");
Run Code Online (Sandbox Code Playgroud)

编译,然后你有你的保证,否则你没有.

更新

究其原因是x2 != y2在更新的是,这些的地址vector<int>vector<vector<int>>.这些内部元素必须找到一个新的(更大的)缓冲区才能生存,就像内部元素一样int.但与之不同的是int,内部元素vector<int>可以使用移动构造函数移动(int必须复制).但无论是移动还是复制,内部元素的地址都必须改变(从小的旧缓冲区到大的新缓冲区).此行为与问题的原始部分一致(内部元素也显示为更改地址).

是的,LWG 2321参与其中,虽然不是一个有争议的问题.在我的回答中,我已经假设LWG 2321已经过去了.除了过于急切地调试迭代器以无偿地(并且错误地)使自己无效之外,真的没有别的办法可以实现.非调试迭代器永远不会失效,指针或引用也不会失效.

希望我有能力轻松创建带有箭头缓冲区的动画.那将是非常清楚的.我只是不知道如何在我有空的时候轻松做到这一点.