一些矢量元素不会改变
Some vector elements do not change
我遇到了非常奇怪的行为,我无法解释。我希望有人能对此有所启发。
首先是代码片段:
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 没有任何改变。
您在 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);
其他人已经提到引用失效,所以我不再赘述。
如果性能很重要,您可以在开始循环之前在原始向量中显式保留足够的 space 用于新边。这可以避免问题,但在技术上仍然是不正确的。即它可以工作,但仍然违反规则。
一种更安全但稍慢的方法是遍历向量,更改现有边并在新向量中生成新边(预先为性能保留足够的 space),然后在最后, 将新矢量附加到现有矢量。
最安全方式(包括完全异常安全),将是创建一个新向量(保留初始向量大小的两倍),遍历初始向量(不修改它的任何边),将 two 新边推入每个旧边的新向量,然后在最后 vector.swap() 将旧向量与新向量矢量.
最后一种方法的一个很大的积极副作用是您的代码要么完全成功,要么保持原始边缘不变。即使面对灾难,它也能保持数据的完整性。
P.S。我注意到你在做:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(oEdge.second, iMiddleVertexIndex)
如果您的其余代码对环方向敏感,您可能想要反转第二条边的参数。即:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(iMiddleVertexIndex, oEdge.second )
我遇到了非常奇怪的行为,我无法解释。我希望有人能对此有所启发。
首先是代码片段:
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 没有任何改变。
您在 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);
其他人已经提到引用失效,所以我不再赘述。
如果性能很重要,您可以在开始循环之前在原始向量中显式保留足够的 space 用于新边。这可以避免问题,但在技术上仍然是不正确的。即它可以工作,但仍然违反规则。
一种更安全但稍慢的方法是遍历向量,更改现有边并在新向量中生成新边(预先为性能保留足够的 space),然后在最后, 将新矢量附加到现有矢量。
最安全方式(包括完全异常安全),将是创建一个新向量(保留初始向量大小的两倍),遍历初始向量(不修改它的任何边),将 two 新边推入每个旧边的新向量,然后在最后 vector.swap() 将旧向量与新向量矢量.
最后一种方法的一个很大的积极副作用是您的代码要么完全成功,要么保持原始边缘不变。即使面对灾难,它也能保持数据的完整性。
P.S。我注意到你在做:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(oEdge.second, iMiddleVertexIndex)
如果您的其余代码对环方向敏感,您可能想要反转第二条边的参数。即:
TEdge(oEdge.first, iMiddleVertexIndex)
TEdge(iMiddleVertexIndex, oEdge.second )