2016-11-02 6 views
1

Я испытываю очень странное поведение, которое я не могу объяснить. Надеюсь, кто-то может пролить свет на него.Некоторые векторные элементы не меняются

Фрагмент кода первой:

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

Ссылка на элемент вектора может быть признана недействительной после вызова push_back – themagicalyang

+1

OT: Не являются ли члены 'станд :: pair' с именем' first'/'second' (а не' start'/'end')? –

+0

@AdrianColomitchi Уверен, что они есть, спасибо за ваше примечание. Исправлен код. – yman

ответ

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); 
+0

Ничего себе. Теперь, когда вы говорите это, это совершенно логично, мне стыдно, что я сам этого не осознавал. Пытаясь обвинить кого-то другого, я сначала подумал, что это следует отметить в документации оператора [] (http://www.cplusplus.com/reference/vector/vector/operator [] /). Но это, безусловно, где-то задокументировано, и это определенно подвержено опыту с контейнерами std, которые мне все еще нужно изучать. Хорошо, спасибо, что помогли мне с этим! – yman

+1

@yman Trie [cppreference.com] (http://en.cppreference.com/w/cpp/container/vector). Пример: 'push_back' документируется с помощью:« Если новый 'size()' больше, чем 'capacity()', то все итераторы и ссылки (включая итератор прошедшего конца) являются недействительными. В противном случае только прошлое -end iterator недействителен ». Честно говоря, это, возможно, самый лучший сайт ссылок на C++ в Интернете, постоянно ухаживается и совершенствуется и поддерживается людьми, которые невероятно знакомы со стандартами. Определенно достоин постоянной закладки на панели инструментов. Удачи. – WhozCraig

1

Другие упоминали о недействительности ссылки, так что я не буду вдаваться в подробности о том, что.

Если производительность имеет решающее значение, вы можете явно зарезервировать достаточное пространство в исходном векторе для новых ребер до начала цикла. Это позволит избежать проблемы, но будет по-прежнему технически неверным. т.е. он будет работать, но все же будет противоречить правилам.

Более безопасный, но немного более медленный способ заключается в том, чтобы перебирать вектор, изменять существующие ребра и генерировать новые ребра в новом векторе (с достаточным пространством, зарезервированным заранее для производительности), а затем в конце добавить новый вектор к существующему.

безопасным способом (в том числе и будучи полностью исключением безопасно), было бы создать новый вектор (резервируя двойной размер начального вектора), перебирать начального вектора (без изменения любого из его краев), толкая новые ребра в новый вектор для каждого старого края, а затем прямо в конце vector.swap() старый вектор с новым вектором.

Большой положительный побочный эффект этого последнего подхода заключается в том, что ваш код либо полностью преуспевает, либо оставляет исходные края неизменными. Он поддерживает целостность данных даже перед лицом катастрофы.

P.S. Я заметил, что вы делаете:

TEdge(oEdge.first, iMiddleVertexIndex) 
TEdge(oEdge.second, iMiddleVertexIndex) 

Если остальная часть кода чувствителен к кольцу-ориентации вы, вероятно, хотите, чтобы полностью изменить параметры для второго края. то есть:

TEdge(oEdge.first,  iMiddleVertexIndex) 
TEdge(iMiddleVertexIndex, oEdge.second  ) 
+0

Спасибо за предложение альтернативных способов. Фактически я не знаю конечного размера в моем реальном приложении, этот сниппет был упрощен таким образом. Поэтому резервирование двойного размера может быть потенциально расточительным. Но ваш вопрос ясен и действителен, мне это нравится. Постскриптум Кольцевая ориентация для меня не проблема. – yman