2015-04-01 2 views
0

Я программирую многопоточный сервер сокетов WIN C++, и я столкнулся с какой-то странной проблемой.Векторная итерация и удаление

Я использую вектор для хранения активных соединений. Я блокирую вектор с помощью мьютекса win, затем пытаюсь перебрать его, чтобы найти все закрытые соединения и удалить их, а затем отпустить мьютекс.

Код:

if (!m_activeConnections.empty()){ 
    for(std::vector<Connection*>::iterator it = m_activeConnections.begin(); it != m_activeConnections.end(); ++it) { 
     if ((*it)->isClosed()){ 
      delete *it; 
      it = m_activeConnections.erase(it); 
      break; 
     } 
    } 
    cout << "\n \t Active Connections: " << m_activeConnections.size() << endl; 
} 

как это работает, но когда я удалить break линии всегда идет еще один цикл с итератора it указывающей на 0Xaaaaaa и бросает исключение. Если это удаление выполняется в том же потоке, где создаются новые соединения, он отлично работает даже без перерыва. Почему это?

ответ

1

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

Простым примером применения этого подхода к вектору является следующий цикл. Обратите внимание, что стирание отменяет все итераторы с момента стирания вперед, поэтому вам нужно получить свежий итератор при стирании, и вам также нужно каждый раз пересчитывать end() (т. Е. Не вывести конечное вычисление из цикла):

for (auto it = m_activeConnections.begin(); it != m_activeConnections.end();) 
{ 
    if ((*it)->isClosed()) { it = m_activeConnections.erase(it); } 
    else     { ++it;        } 
} 

лучший способ удалить из вектора состоит в перемещение чтобы быть стертыми элементов к задней части вектора, а затем стирать весь спектр все сразу и избежать перемещения диапазона хвоста вокруг все времени. Как правило, мы делаем это с remove_if, хотя вам нужно добавить немного хитрости также удалить pointees в вашем случае:

m_activeConnections.erase(
    std::remove_if(m_activeConnections.begin(), 
        m_activeConnections.end(), 
        [](Connection * p) { 
         if (p->isClosed()) { delete p; return true; } 
         return false;}), 
    m_activeConnections.end()); 

Вы можете избежать этого обмана, если вы изменили свой контейнер std::vector<std::unique_ptr<Connection>>: Есть один класс за ответственность (вектор содержит, единственный указатель удаляет), и алгоритмы становятся составными.

Если вы не можете сделать ваш код простым путем выбора соответствующих абстракций, вы также можете попробовать более сложный алгоритм: Partition диапазон первого в соответствии с необходимостью удаления, а затем удалить, а затем удалить диапазон:

auto it = std::stable_partition(m_activeConnections.begin(), 
           m_activeConnections.end(), 
           [](Connection * p) { return p->isClosed(); }); 

for (auto kt = it; kt != m_activeConnections.end(); ++kt) 
{ 
    delete *kt; 
} 

m_activeConnections.erase(it, m_activeConnections.end()); 
+0

О, спасибо, я новичок в C++. Я привык к java и не обращал на это внимания. Спасибо. – jack

+1

На самом деле, 'remove_if' не будет работать здесь, если целью является как« удалить »элемент, так и удалить элемент. Если алгоритмный подход должен быть использован, сначала 'std :: stable_parttion', а затем' delete' и стирание элементов на «плохой» стороне раздела. – PaulMcKenzie

+0

@PaulMcKenzie: Хорошо, я обновил ответ! –

Смежные вопросы