Удаление необработанных указателей из std :: vector
У меня есть следующая картина:
- У меня есть
std::vector
содержащий необработанные указатели на объекты (я знаю, что необработанные указатели являются "злыми", но это устаревшее программное обеспечение, которое необходимо поддерживать). - Теперь для каждого элемента в векторе мне нужно сделать тест, и если тест положительный, сделать что-то с указателем, удалить его, а затем удалить его из вектора:
Псевдокод:
for each pointer in vector
{
if (SomeTest(pointer))
{
DoSomething(pointer)
delete pointer
remove pointer from vector
}
}
Я не могу придумать хороший чистый код для этого.
Эта ссылка предоставляет различные подходы, но все они выглядят более или менее громоздкими для меня.
Громоздкое решение, которое я использую сейчас:
for(auto & p : v)
{
if (SomeTest(p))
{
DoSomething(p);
delete p;
p = nullptr;
}
}
v.erase(std::remove(v.begin(), v.end(), nullptr), v.end());
Ответы
Ответ 1
Как часто ответ таков: знай свой <algorithm>
(и это хорошее напоминание для меня);)
std::partition
- это то, что вы ищете: std::partition(begin, end, p)
"перемещает" элементы диапазона [ begin
, end
), которые не удовлетворяют предикату p
в конце диапазона; затем вы можете рассматривать их как пакет.
auto const to_be_removed = std::partition(begin(v), end(v), [](auto p){ /* predicate */ });
std::for_each(to_be_removed, end(v), [](auto p) {
/* crunch */
delete p;
});
v.erase(to_be_removed, end(v));
Полная программа
#include <iostream>
#include <algorithm>
#include <vector>
int main()
{
std::vector v = { new int{0}, new int{1}, new int{2} };
// let delete all even values
auto const to_be_removed = std::partition(begin(v), end(v), [](auto p){ return *p % 2 != 0; });
std::for_each(to_be_removed, end(v), [](auto p) {
std::cout << "Deleting value " << *p << "...\n";
delete p;
});
v.erase(to_be_removed, end(v));
}
Live демо
Идти дальше
Эта реализация имеет два основных недостатка: порядок из вектора нестабилен (1), он может быть преобразован в функцию многократного использования (2).
- (1) решается с помощью
std::stable_partition
. - (2) это не так сложно:
template<class InputIt, class UnaryPredicate, class UnaryDeleter>
InputIt delete_if(InputIt begin, InputIt end, UnaryPredicate p, UnaryDeleter d)
{
auto const to_be_removed = std::stable_partition(begin, end, std::not_fn(p));
std::for_each(to_be_removed, end, [d](auto p) { d(p) ; delete p; });
return to_be_removed;
}
template<class Container, class UnaryPredicate, class UnaryDeleter>
auto delete_if(Container& c, UnaryPredicate p, UnaryDeleter d)
{
using std::begin, std::end;
return c.erase(delete_if(begin(c), end(c), p, d), end(c));
}
Использование:
delete_if(v, SomeTest, DoSomething);
Live демо
Ответ 2
Вы можете использовать std::remove_if
Я не уверен, почему статья, на которую вы std::remove_if
использует std::remove_if
перед удалением указателей, потому что это не сработает. Вы должны удалить указатели перед удалением:
std::vector<int*> v;
v.erase(std::remove_if(std::begin(v), std::end(v), [](int* p){
// do your test and do not remove on failure
if(!SomeTest(p))
return false; // keep this one
DoSomething(p);
// Now we remove but be sure to delete here, before the
// element is moved (and therefore invalidated)
delete p;
return true; // signal for removal
}), std::end(v));
Примечания: почему это безопасно.
Удаление указателя изменяет не сам указатель, а объект, на который указывает указатель. Это означает, что ни один элемент не изменяется с этим подходом.
Стандарт C++17 28.6.8 5
гарантирует, что предикат будет вызываться только один раз для каждого элемента.
Ответ 3
Самое простое решение - начиная со связанной статьи - это взять функцию erase_if
template <typename Container, typename Pred>
void erase_if(Container &c, Pred p)
{
c.erase(std::remove_if(std::begin(c), std::end(c), p), std::end(c));
}
и просто позвонить с
erase_if(v, [](T *pointer)
{
if (SomeTest(pointer))
{
DoSomething(pointer);
delete pointer;
return true; //remove pointer from vector
}
return false;
});
Очевидно, что вы можете разделить свой предикат на две части, если хотите отделить часть SomeTest/DoSomething от части delete
:
template <typename Container, typename Pred>
void delete_if(Container &c, Pred p)
{
auto e = std::remove_if(std::begin(c), std::end(c),
[&p](Container::value_type *pointer)
{
if (p(pointer)) {
delete pointer;
return true;
}
return false;
});
c.erase(e, std::end(c));
}
Поскольку вы не сказали, почему вам не нравится erase_if
вы связали сами, я не могу догадаться, имеет ли это ту же проблему.
Ответ 4
При следующем подходе сначала вы разделяете удаляемые элементы, затем удаляете, а затем корректируете вектор.
auto badIt = std::stable_partition(std::beging(v), std::end(v), SomeTestInverse);
std::for_each(badIt, std::end(v), [](auto e){ DoSomething(e); delete e;});
v.erase(badIt,std::end(v));
Предоставленный предикат должен быть истинным, чтобы элемент оставался в рабочем состоянии, поскольку элементы, не прошедшие предикат, находятся в последнем диапазоне.
Ответ 5
Предполагается, что у вас есть вектор указателя int. Вот мое решение:
vector<int*> vpi;
for (vector<int*>::iterator it = vpi.begin(); it != vpi.end(); )
{
if (SomeTest(*it))
{
DoSomething(*it)
int* old = *it;
it = vpi.erase(it);
delete old;
} else
{
it++;
}
}
Ответ 6
Какой-то подход, который я бы использовал:
for (auto i = vector.begin(); i != vector.end(); ++i) {
if (SomeTest(*i)) {
DoSomething(*i);
delete *i;
*i = nullptr;
}
}
vector.erase(std::remove(vector.begin(), vector.end(), nullptr), vector.end());
Ответ 7
Будь проще. YAGNI. Нет причин решать более общую и сложную версию проблемы, если вам она понадобится (подсказка: вы не будете) или выискиваете неясные методы STL (ну, если вы этого не хотите).
size_t target = 0;
for (size_t idx = 0; idx < v.size(); idx++) {
if (should_delete(v[idx]))
delete v[idx];
else
v[target++] = v[idx];
}
v.resize(target);