У меня есть вектор, которого я выполняю итерации. При итерации я могу добавить новые значения к вектору. Это смотрит что-то как:
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);
}
Там какие-либо лучшие практики должны обработать случаи как это? Комментирует цикл с "Предупреждением: Этот цикл, намеренно добавляет к вектору при итерации. Изменить осторожно" лучший подход? Я открыт для переключающихся контейнеров также, если это делает вещи более устойчивыми.
Если кто-то другой придет и поправит цикл, его легко разорвать.
Тогда не используйте for
цикл , используйте вместо него цикл while
. Для меня цикл for
всегда подразумевает простой итерационный цикл с использованием счетчика . Однако, если я сталкиваюсь с циклом while
, мне кажется, что все было слишком сложно, чтобы выразить их в простом цикле for
. Я буду смотреть внимательнее и более осторожен с "оптимизацией" циклов while
, чем с for
циклов.
Мой подход к этой проблеме часто заключается в создании очереди, в которую я добавляю все новые элементы, а затем, после итерации исходного контейнера, обрабатываю элементы в очереди и/или добавляю их к исходному.
Положительные стороны этого подхода в том, что происходящее очевидно, и он работает в сценариях, где несколько потоков могут ставить в очередь новые элементы.
Разрешить 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
Вам нужен произвольный доступ к этому вектору? Если нет, то std :: list
более подходит. Я лично считаю, что добавление к вектору во время его итерации - не лучшая идея.
Вы можете добавить в двухстороннюю очередь, не делая недействительными итераторы ее элементов. Произвольный доступ близок по эффективности к вектору, поэтому часто его лучше заменить.
Не слишком отличается от оригинала. Просто уточню некоторые моменты:
for (vector<Foo>::size_type i = 0, n = vec.size(); i < n; ++i)
if (vec[i].condition){
AppendToVec(vec);
n = vec.size();
}
Как уже указывалось, и как вы уловили, здесь есть проблема. Однако у вас есть несколько возможных решений, так что не заряжайте вслепую.
WARNING
или любым другим, что требует ваш стандарт кодирования, чтобы предупредить будущего сопровождающего о том, что здесь есть какая-то уловка. Лучше всего не использовать, но может сработать. резерв
и вообще предотвратить перераспределение. std :: deque
. Большинство характеристик производительности схожи, однако вы можете добавлять и добавлять новые значения без аннулирования итераторов / ссылок и т. Д. Здесь выглядит естественным образом Я думаю, что deque
- лучшее решение. Он соответствует вашему алгоритму, и вам не нужно беспокоиться о проблемах. Вероятно, вы могли бы заменить большую часть вектора
в своем коде на deque
. И если вы не хотите изменять интерфейс:
в двухстороннюю очередь
двухсторонней очереди
в вектор
не будет включать намного больше копий, чем просто дважды перераспределение вектора
. Так что не стесняйтесь!
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());
}