假设有一个包含int vector 的A类。现在假设创建了一个A的 vector 。例如,如果由于push_back而发生了A对象的重新分配(因此 vector 对象已移动),指向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";
}

运行这段代码可以得到:
0x1810088 - 0x1810028 - 2
0x1810088 - 0x18100c8 - 30

因此它表明指针仍然有效。但是我要确保这是有保证的,而不仅仅是偶然的事情。我倾向于说这是有保证的,因为 vector 的内部数据是动态分配的,但是,再次,只是想检查一下。

我看过这里[Iterator invalidation rules],但它没有考虑这种特定情况(即, vector 对象本身的重新分配)。

更新:

我尝试以此检查我在评论中为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";

并得到了:
0x16f0098 - 0x16f0048 - 2
0x16f0098 - 0x16f00c8 - 30

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

最佳答案

不幸的是,这不能保证。话虽如此,当前所有三个实现(libc++,libstdc++和VS-2015)似乎都可以保证这一点。问题是A的move构造函数是否为noexcept:

static_assert(std::is_nothrow_move_constructible<A>::value, "");
A的move构造函数由编译器提供,因此取决于std::vector<int>的move构造函数。如果std::vector<int>的move构造函数为noexcept,则A的move构造函数为noexcept,否则不是。

当前的草案N4296并未将vector的move构造函数标记为noexcept。但是,它允许实现。

这行:
aVec.resize(30);

如果move构造函数不是noexcept,则将使用A的move构造函数,否则将使用A的copy构造函数。如果使用A的副本构造函数,则int的位置将更改。如果使用A的move构造函数,则int的位置将保持稳定。

libc++和libstdc++将vector的move构造函数标记为noexcept。因此,给A一个noexcept move构造函数。

VS-2015表示A没有noexcept move构造函数:
static_assert(std::is_nothrow_move_constructible<A>::value, "");

不编译。

但是,VS-2015不会将整数重新分配给新地址,因此看起来它不符合C++ 11规范。

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

委员会最近的讨论表明,每个人都赞成标记vector noexcept(也可能是basic_string,但不是其他容器)的move构造函数。因此,将来的标准可能会保证您寻求的稳定性。同时,如果:
static_assert(std::is_nothrow_move_constructible<A>::value, "");

编译,那么您就有了保证,否则,您没有保证。

更新

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

是的,虽然没有争议,但涉及到LWG 2321。在我的回答中,我已经假定LWG 2321已通过。除了过度渴望调试迭代器无偿(无效地)使自身无效之外,实际上没有其他事情可以发生。无调试的迭代器永远不会使指针无效,引用或引用也不会无效。

希望我能够轻松创建带有箭头的动画到缓冲区。那真的很清楚。我只是不知道在我有空的时候如何轻松地做到这一点。

关于c++ - 重新分配std::vector对象时指向内部数据结构的指针的有效性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27735754/

10-11 22:50
查看更多