一些矢量元素不会改变

时间:2016-11-02 08:24:03

标签: c++ stdvector

我遇到了非常奇怪的行为,我无法解释。我希望有人可以对此有所了解。

首先是代码段:

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并没有改变任何东西。

2 个答案:

答案 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      )