简体   繁体   English

C ++:指针向量在push_back()之后失去引用

[英]C++: vector of pointer loses the reference after push_back()

In my code a have a global vector of Node object and a local vector of Node pointers: 在我的代码中,a有一个Node对象的全局向量和一个Node指针的局部向量:

#include<cstdio>
#include<cstdlib>
#include<vector>

using namespace std;

class Node {
    int n;

public:
    Node(int i) : n(i);
    int getN() { return n; }
};

vector<Node> v;

int main() {
    vector<Node*> p;
    v.push_back(Node(1));
    p.push_back(&v[0]);
    printf("first node id : %d\n", (*p[0]).getN());

    return 0;
}

I inserted a node object to global vector & inserted the pointer of that object in the local vector. 我将一个节点对象插入到全局向量中,并将该对象的指针插入到本地向量中。 Output of my above code is: 我上面代码的输出是:

first node id : 1

However, if I change my main function to this: 但是,如果我将主要功能更改为:

int main()
{
    vector<Node*> p;
    v.push_back(Node(1));
    p.push_back(&v[0]);
    v.push_back(Node(2));
    p.push_back(&v[1]);
    printf("first node id : %d\n", (*p[0]).getN());

    return 0;
}

The code prints a garbage value: 代码打印垃圾值:

first node id : 32390176

I can't figure out the problem. 我无法弄清楚这个问题。 Does the vector data structure changes the references of each object after an insertion ? vector数据结构是否会在插入后更改每个对象的引用? How can I fix this ? 我怎样才能解决这个问题 ?

"Does a vector change the references after an insertion?" “插入后向量是否会更改引用?”

Possibly, yes. 可能,是的。 An std::vector may reallocate its (heap) storage when you add/ push_back() additional elements, invalidating all pointers: 当你添加/ push_back()其他元素时, std::vector可以重新分配它的(堆)存储,使所有指针无效:

Iterator [read: Pointer] Invalidation 迭代器[读取:指针]失效

(for operations) push_back , emplace_back ... If the vector changed capacity, all of them [ie all iterators are invalidated]. (用于操作) push_backemplace_back ...如果向量改变了容量,则所有这些[即所有迭代器都无效]。 If not, only end() . 如果没有,只有end()

"How can I fix this?" “我怎样才能解决这个问题?”

The above invalidation rule does not apply if a vector's capacity does not change due to an insertion - since vectors do not reallocate storage unnecessarily. 如果向量的容量由于插入而没有改变,则上述失效规则不适用 - 因为向量不会不必要地重新分配存储。 So if you pre-set your vector's capacity to 2 in your example (say, with v.reserve(2) ), the pointer will remain valid. 因此,如果您在示例中预先将矢量容量设置为2(例如,使用v.reserve(2) ),则指针将保持有效。 If you don't know the size in advance, but you can delay the construction of the second vector (with the pointers), you don't have to reserve, you'll just have the size after inserting the last element. 如果你事先不知道大小,但你可以延迟第二个向量的构造(使用指针),你不必保留,你只需要插入最后一个元素后的大小。

The approaches above are highly unrecommended , however. 然而,上述方法是非常不受推荐的 If you were to make your vector constant - at least in the scope of a function in which you would construct and use the second vector - you would have a strong guarantee of non-reallocation. 如果你要使向量保持不变 - 至少在你将构造和使用第二个向量的函数范围内 - 你将有一个强大的非重新分配保证。 Alternatively, if you could determine the size in advance you might use an std::array , and it would be more fitting to use pointers into that container's storage: 或者,如果您可以提前确定大小,则可以使用std::array ,并且将指针用于该容器的存储更合适:

Iterator Invalidation 迭代器失效

As a rule, iterators to an array are never invalidated throughout the lifetime of the array. 通常,数组的迭代器在数组的整个生命周期中永远不会失效。

You might also consider storing indices into your vector (although there, as well, the vector might shrink, invalidating the indices, or you might insert elements in the middle etc). 您也可以考虑将索引存储到向量中(尽管也可以使向量收缩,使索引无效,或者可以在中间插入元素等)。

Anyway, I suspect that you might not actually want to do any of this, ie it seems to be a not-so-good solution to a problem which could be handled with a different approach altogether. 无论如何,我怀疑你可能实际上并不想做任何这样的事情,即它似乎是一个不太好的解决方案,可以用一个完全不同的方法处理问题。

PS - If the vector has a custom allocator then everything I've written might be irrelevant. PS - 如果向量具有自定义分配器,那么我写的所有内容都可能无关紧要。

What you are doing is undefined behavior for your vector p because the vector v can change where it's objects are stored. 你正在做的是矢量p未定义行为,因为矢量v可以改变存储对象的位置。

A std::vector 's memory is contiguous, so it may, after a number of push_backs , have to allocate a new block memory and copy it's contents to the new block. std::vector的内存是连续的,因此在一些push_backs之后,它可能必须分配一个新的块内存并将其内容复制到新块。 This will invalidate all the pointers that happened to point to the old memory location. 这将使所有指向旧内存位置的指针无效。

Yes, a push_back() on a vector invalidates all references (and pointers) to elements in that vector if it has to reallocate. 是的,如果必须重新分配,向量上的push_back()将使该向量中的元素的所有引用(和指针)无效。 There are various ways to work around this. 有各种方法可以解决这个问题。 If you know that your vector will have a particular number of nodes, you can use reserve() . 如果您知道向量将具有特定数量的节点,则可以使用reserve() In your example, you could reserve two elements: 在您的示例中,您可以保留两个元素:

int main()
{
    v.reserve(2);
    .
    .
    .
}

This will make sure the vector has preallocated enough storage so that it doesn't need to reallocate. 这将确保向量已预分配足够的存储空间,因此无需重新分配。

If you don't know the size ahead of time, then you'll have to change something about your approach. 如果你不提前知道尺寸,那么你将不得不改变你的方法。 You might use a std::deque instead of a std::vector , since std::deque doesn't invalidate references when you use push_back() . 您可以使用std::deque而不是std::vector ,因为当您使用push_back()时, std::deque不会使引用无效。 You might store indices instead of pointers. 您可以存储索引而不是指针。 Or you might need to push all the nodes into your vector before making the pointers. 或者您可能需要在制作指针之前将所有节点推送到向量中。

int main()
{
    v.push_back(Node(1));
    v.push_back(Node(2));

    vector<Node*> p;
    p.push_back(&v[0]);
    p.push_back(&v[1]);

    printf("first node id : %d\n", (*p[0]).getN());

    return 0;
}

You have stumbled upon one of the great known "dark corners" of C++: the great "iterator invalidation." 你偶然发现了C ++中一个众所周知的“黑暗角落”:伟大的“迭代器失效”。 Definitely familiarize yourself intimately with this: 绝对熟悉自己:

Iterator invalidation rules 迭代器失效规则

In particular, you are hitting this reality: 特别是,你正在打击这个现实:

vector : all iterators and references before the point of insertion are unaffected, unless the new container size is greater than the previous capacity (in which case all iterators and references are invalidated) [23.2.4.3/1] vector :插入点之前的所有迭代器和引用都不受影响, 除非新容器大小大于先前的容量(在这种情况下,所有迭代器和引用都无效) [23.2.4.3/1]

(the emphasis is mine) (重点是我的)

Now, about your issue. 现在,关于你的问题。 You can either make sure the vector never re-allocates. 您可以确保向量永远不会重新分配。 Or, you can use a different container that does not have this issue. 或者,您可以使用不存在此问题的其他容器。 There are compromises among all the container types, depending on your needs. 根据您的需要,所有容器类型都有妥协。 Check out the other question thoroughly and make an informed decision. 彻底检查另一个问题并做出明智的决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM