повторяю вектор, удаляю определенные элементы по мере продвижения
-
05-07-2019 - |
Вопрос
У меня есть std::векторный m_vPaths;Я буду повторять этот вектор и вызывать ::DeleteFile(strPath) по ходу работы.Если я успешно удалю файл, я удалю его из вектора.Мой вопрос в том, могу ли я обойти необходимость использования двух векторов?Существует ли другая структура данных, которая могла бы лучше подойти для того, что мне нужно сделать?
пример:использование итераторов почти делает то, что я хочу, но проблема в том, что как только вы стираете с помощью итератора, все итераторы становятся недействительными.
std::vector<std::string> iter = m_vPaths.begin();
for( ; iter != m_vPaths.end(); iter++) {
std::string strPath = *iter;
if(::DeleteFile(strPath.c_str())) {
m_vPaths.erase(iter);
//Now my interators are invalid because I used erase,
//but I want to continue deleteing the files remaining in my vector.
}
}
Я могу использовать два вектора, и у меня больше не будет проблем, но есть ли лучший, более эффективный метод выполнения того, что я пытаюсь сделать?
кстати, если это неясно, m_vPaths объявляется следующим образом (в моем классе):
std::vector<std::string> m_vPaths;
Решение
Проверьте std::remove_if
:
#include <algorithm> // for remove_if
#include <functional> // for unary_function
struct delete_file : public std::unary_function<const std::string&, bool>
{
bool operator()(const std::string& strPath) const
{
return ::DeleteFile(strPath.c_str());
}
}
m_vPaths.erase(std::remove_if(m_vPaths.begin(), m_vPaths.end(), delete_file()),
m_vPaths.end());
Используйте std::list
чтобы остановить проблему с недопустимыми итераторами, хотя вы теряете произвольный доступ.(И производительность кэша в целом)
Для протокола, способ, которым вы бы реализовали свой код, был бы:
typedef std::vector<std::string> string_vector;
typedef std::vector<std::string>::iterator string_vector_iterator;
string_vector_iterator iter = m_vPaths.begin();
while (iter != m_vPaths.end())
{
if(::DeleteFile(iter->c_str()))
{
// erase returns the new iterator
iter = m_vPaths.erase(iter);
}
else
{
++iter;
}
}
Но вы должны использовать std::remove_if
(изобретать велосипед заново - это плохо).
Другие советы
В erase()
метод возвращает новый (допустимый) итератор, который указывает на следующий элемент после удаленного.Вы можете использовать этот итератор для продолжения цикла:
std::vector<std::string>::iterator iter;
for (iter = m_vPaths.begin(); iter != m_vPaths.end(); ) {
if (::DeleteFile(iter->c_str()))
iter = m_vPaths.erase(iter);
else
++iter;
}
Учитывая время на удаление файла, это, вероятно, не имеет значения, но я бы все же посоветовал выполнить итерацию по вектору в обратном направлении - таким образом, вы обычно удаляете элементы из (близко к) концу вектора.Время, затраченное на удаление элемента, пропорционально количеству элементов, следующих за ним в векторе.Если (например) у вас есть вектор из 100 имен файлов, и вы успешно удалили все из них, вы скопируете последний элемент 100 раз в процессе (и скопируете предпоследний элемент 99 раз и так далее).
OTOH, если вы начнете с конца и будете работать в обратном направлении, вы не будете копировать, пока удаление файлов проходит успешно.Вы можете использовать обратные итераторы для обхода вектора в обратном направлении, практически ничего не изменяя.Например, код GMan, использующий remove_if, должен продолжать работать (только немного быстрее), просто заменив rbegin() на begin() и rend() на end.
Другая возможность заключается в использовании deque вместо вектора - deque может стирать элементы с конца или начало сбора в постоянное время.