Подтвердить что ты не робот

Добавляем к вектору, итерации по нему?

У меня есть вектор, который я повторяю. Во время итерации я могу добавлять новые значения к вектору. Это выглядит примерно так:

struct Foo
{
   bool condition;
};

void AppendToVec(vector<Foo>& v)
{
   ...
   v.push_back(...);
}

vector<Foo> vec;
...
for (vector<Foo>::size_type i = 0; i < vec.size(); ++i)
{
   if (vec[i].condition) AppendToVec(vec);
}

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

//No longer iterates over newly appended elements
vector<Foo>::size_type size = vec.size();
for (vector<Foo>::size_type i = 0; i < size; ++i)
{
   if (vec[i].condition) AppendToVec(vec);
}

или

//Vector resize may invalidate iterators
for (vector<Foo>::iterator i = vec.begin(); i != vec.end(); ++i)
{
   if (vec->condition) AppendToVec(vec);
}

Есть ли какие-либо рекомендации по работе с такими случаями? Комментирует цикл с помощью "Предупреждение: этот цикл намеренно присоединяется к вектору во время итерации. Измените осторожно" лучший подход? Я открыт для переключения контейнеров, если это делает вещи более надежными.

4b9b3361

Ответ 1

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

Хорошим моментом для этого подхода является то, что происходящее очевидно, и оно работает в сценариях, где несколько потоков могут захватывать новые элементы.

Ответ 2

Если кто-то еще подходит и настраивает петлю, ее можно легко сломать.

Затем не использовать цикл for, вместо этого используйте цикл while. Для меня цикл a for всегда подразумевает простой итеративный цикл с использованием счетчика. Однако, если я столкнулся с циклом while, я чувствую, что все должно быть слишком сложно, чтобы выразить их в простом цикле for. Я посмотрю поближе, и я буду более осторожен с циклами "оптимизация" while, чем с циклами for.

Ответ 3

Разрешить AppendToVec обновлять i, если vec было перераспределено с использованием относительной позиции в старом векторе (i-vec.begin()).

Код:

void AppendToVec(vector<int> & vec, vector<int>::iterator & i)
{
    const int some_num = 1;
    const size_t diff = i-vec.begin();
    vec.push_back(some_num);
    i = vec.begin()+diff;
}

int main(int argc, char* argv[])
{    
     const size_t arbit_size = 10;
     const size_t prior_size = 3;
     vector<int> vec;

     // Fill it up with something
     for(size_t i = 0; i < prior_size; ++i) vec.push_back(static_cast<int>(i));

     // Iterate over appended elements
     vector<int>::iterator i = vec.begin();
     while(i != vec.end())
     {
      cout<<*i<<","<<vec.size()<<endl;
      if(vec.size()<arbit_size) AppendToVec(vec,i);
      ++i;
     }

     return 0;
}

Вывод:

0,3
1,4
2,5
1,6
1,7
1,8
1,9
1,10
1,10
1,10

Ответ 4

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

  • Большой толстый комментарий в верхней части цикла с тегом WARNING или любым другим вашим стандартом кодирования, чтобы предупредить будущего сопровождающего о том, что есть обман. Лучше всего не использовать, но может работать.
  • Если вы в состоянии заранее знать, сколько элементов будет добавлено (или у вас есть относительно жесткая верхняя граница), вы можете использовать reserve и вообще предотвратить перераспределение.
  • Использование std::deque. Большинство характеристик производительности схожи, однако вы можете добавлять и добавлять новые значения без аннулирования итераторов/ссылок и т.д.... выглядит естественным образом здесь.
  • Использование отдельной очереди и двойной петли

Я думаю, что deque - лучшее решение здесь. Он соответствует вашему алгоритму, и вам не нужно беспокоиться о проблемах. Вероятно, вы могли бы заменить большую часть vector вашего кода на deque. И если вы не хотите менять интерфейс:

  • Скопируйте vector в deque
  • Compute
  • Назначьте содержимое deque в vector

не будет содержать гораздо больше копий, чем просто перераспределение vector в два раза. Так что не стесняйтесь!

void treat(std::vector<int>& vec)
{
   // WARNING: we use a deque because of its iterator invalidation properties
   std::deque<int> deq(vec.begin(), vec.end());

   for (std::deque<int>::const_iterator it = deq.begin(); it != deq.end(); ++it)
   {
     if (it->condition()) deq.push_back(func(*it));
   }

   vec.assign(deq.begin(), deq.end());
}

Ответ 5

Частый комментарий часто достаточно, чтобы сделать это ясно, например.

for (vector<Foo>::size_type i = 0; i < vec.size() /* size grows in loop! */; ++i)
{
   if (vec[i].condition) AppendToVec(vec);
}

Ответ 6

Вам нужен случайный доступ к этому вектору? Если нет, более подходит std::list. Это мое личное мнение о том, что добавление к вектору в то время как итерация по нему не является хорошей идеей.

Ответ 7

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

Ответ 8

Не слишком отличается от оригинала. Просто сделайте здесь несколько вещей:

for (vector<Foo>::size_type i = 0, n = vec.size(); i < n; ++i)
  if (vec[i].condition){
    AppendToVec(vec);
    n = vec.size();
  }