我遇到了非常奇怪的行为,我无法解释。我希望有人可以对此有所了解。
首先是代码段:
class TContour {
public:
typedef std::pair<int,int> TEdge; // an edge is defined by indices of vertices
typedef std::vector<TEdge> TEdges;
TEdges m_oEdges;
void splitEdge(int iEdgeIndex, int iMiddleVertexIndex) {
TEdge & oEdge = m_oEdges[iEdgeIndex];
m_oEdges.push_back(TEdge(oEdge.first, iMiddleVertexIndex));
oEdge = TEdge(oEdge.second, iMiddleVertexIndex); // !!! THE PROBLEM
};
void splitAllEdges(void) {
size_t iEdgesCnt = m_oEdges.size();
for (int i=0; i<iEdgesCnt; ++i) {
int iSomeVertexIndex = 10000; // some new value, not actually important
splitEdge(i, iSomeVertexIndex);
}
};
};
当我调用splitAllEdges()
时,会更改原始边缘并添加新边缘(导致容器大小加倍)。一切都如预期,除了1个原始边缘,不会改变。如果有任何兴趣,其索引为3
,值为[1,242]
。所有其他原始边缘都会改变,但这个边缘保持不变。添加调试打印确认边缘使用不同的值写入,但m_oEdges
内容不会更改。
我有一个简单的解决方法,用m_oEdges[iEdgeIndex] = TEdge(oEdge.end, iMiddleVertexIndex);
替换有问题的行确实解决了这个问题。虽然我担心的是意外行为的原因是什么。可能是一个编译器错误(因此我还有其他问题需要什么?),还是我忽略了代码中的一些愚蠢错误?
/usr/bin/c++ --version
c++ (Debian 4.9.2-10) 4.9.2
从c ++ 98切换到c ++ 11并没有改变任何东西。
答案 0 :(得分:3)
您在push_back操作后使用无效引用。
此:
TEdge & oEdge = m_oEdges[iEdgeIndex];
获取参考。然后这个:
m_oEdges.push_back(TEdge(oEdge.start, iMiddleVertexIndex));
可能调整向量的大小,这样做会使oEdge
引用无效。在这一点上:
oEdge = TEdge(oEdge.end, iMiddleVertexIndex);
不再定义行为,因为您正在使用悬空参考。重用索引,而不是引用,例如:
m_oEdges[iEdgeIndex] = TEdge(m_oEdges[iEdgeIndex].end, iMiddleVertexIndex);
答案 1 :(得分:1)
其他人提到了参考文献的无效,所以我不会详细介绍。
如果性能至关重要,您可以在开始循环之前在原始矢量中为新边显式保留足够的空间。这样可以避免这个问题,但在技术上仍然是不正确的。即它会起作用,但仍然违反规则。
更安全但速度稍慢的方法是迭代向量,更改现有边并在新向量中生成新边(预先为性能预留足够的空间),然后在最后,将新向量附加到现有的。
最安全方式(包括完全异常安全),将创建一个新的向量(保留初始向量的两倍),迭代初始向量(不修改任何edge),将两个新边缘推入每个旧边缘的新向量,然后在最后向量vector.swap()使用新向量推送旧向量。
最后一种方法的一个重要的积极副作用是你的代码要么完全成功,要么保持原始边缘不变。即使在灾难面前,它也能保持数据的完整性。
P.S。我注意到你在做:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(oEdge.second, iMiddleVertexIndex)
如果其余代码对于环形方向敏感,则可能需要反转第二条边的参数。即:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(iMiddleVertexIndex, oEdge.second )