C++中奇怪的引用行为

4

我有一个非常简短的问题:

vector<int> ints{1,2,3,4};

int &y = ints[0];

// ints.push_back(y);

y = 5;

for (auto &x : ints) {
    cout << x << ", ";
}

为什么当你加入注释后,你会得到5,2,3,4,但当你取消注释ints.push_back(y);时,你会得到1,2,3,4,1。为了更清楚地表明问题,我会再写一遍:你得到的是[-->1<--], 2, 3, 4, 1,而不是5,2,3,4,1甚至5,2,3,4,5
这种行为让我发疯......底层发生了什么?

1
std::vector::push_back() 接受的是一个副本,而不是一个引用。 - πάντα ῥεῖ
很好,但为什么它会破坏引用,我强调你得到"[-->1<--], 2, 3, 4, 1"。 - Lu4
未定义行为。push_back可能会使指向向量内容的所有引用/指针无效。 - Oliver Charlesworth
这不是使用容器的正确方式... - icaptan
这是在通过引用迭代容器时的典型操作(因为您不想复制大型结构体/嵌套向量值)。 - Lu4
3个回答

15

当您在向向量推送数据时,如果该向量没有足够的内存来存储新元素,则会分配一个更大的内存块,将元素移动到其中,然后释放旧内存。如果发生这种情况,则向量中的任何元素的引用或指针都将无效。

因此,在 push back 操作之后,当您将 5 赋值给 y 时,这是未定义的行为,因为 y 是无效的引用,不应该期望此赋值对向量中的任何元素产生影响。


1
@Lu4 嗯,标准和一些参考网站明确指出,插入时引用可能会中断... - deviantfan

4

在调用std::vector::push_back时,您需要考虑可能发生的重新分配。

int &y = ints[0];

ints.push_back(y);

y = 5;

第三个指令可能会导致未定义的行为,如果进行了这样的重新分配。如果发生这种情况,您将失去所有可以想到的保证。 通过reserv预留内存,可以显示预期结果。

5, 2, 3, 4, 1,

std::vector<int> ints{1,2,3,4};
ints.reserve(128); // Note

int &y = ints[0];

ints.push_back(y);

y = 5;
cppreference网站上的表格清楚地展示了何时会使迭代器失效。

通常情况下,不应该在容器中存储可能会失效的元素的自由引用或指针;如果真的需要这样做,请重新加载地址或选择提供更多关于重新分配的保证的容器,例如std::dequestd::forward_liststd::list


1
我相信你是在推回一个副本。原因是你的向量持有类型int而不是类型&int,所以它通过进行解引用复制来隐式转换引用。
在push_back被注释的示例中,你没有将副本添加到向量的末尾,仍然有一个指向第一个元素的引用,这意味着你可以通过引用更改该元素。但是一旦你执行push_back,向量将被重新调整大小,并移动其中的对象,使任何引用或指向这些对象的指针无效。
你可以通过在构建向量时将其设置为更大的大小来清楚地看到这一点。即:
vector<int> ints(5);
ints = {1,2,3,4};

int &y = ints[0];

ints.push_back(y);

y = 5;

for (auto &x : ints) {
    cout << x << ", ";
}

// output = 5,2,3,4,1

1
你的第二句话很难理解。 - M.M
已编辑以确保清晰。谢谢。 - Sam Redway
你没有改变第二句话...(以“原因是”开头) - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接