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

标签 c++ vector stl

假设有一个类 A 包含一个整数 vector 。现在假设创建了一个 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。

最佳答案

很遗憾,这无法保证。话虽如此,所有 3 个当前实现(libc++、libstdc++ 和 VS-2015)似乎都可以保证这一点。问题是 A 的移动构造函数是否是noexcept:

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

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

当前草案 N4296 未标记 vector 的移动构造函数作为无异常(exception)。但是它允许实现这样做。

这一行:

aVec.resize(30);

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

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

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

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

不编译。

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

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

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

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

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

更新

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

是的,LWG 2321涉及,虽然不是一个有争议的点。在我的回答中,我已经假设 LWG 2321已通过。除了过度急切地调试迭代器以无偿(并且错误地)使自己无效之外,真的没有其他方法可以让事情发生。非调试迭代器永远不会失效,指针或引用也不会。

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

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

相关文章:

java - 如何在 Java 代码中保留 C++ 对象?可能的?

c++ - 自定义类的正确构造函数

c++ - 在构造函数中填充类指针 vector

c++ - 填充 std::vector 时出现错误分配错误

python - lldb 中用于 STL 的 pretty-print

c++ - 两行C++代码的问题

c++ - 如何建立facebook的抄写员?

c++ - 将标志传递给方法的替代方法?

c++ - 如何对一个元素使用 vector<T>::reverse_iterator

c++ - 从概念上理解容器上的位置访问操作