从 std::vector 中删除原始指针
Deleting raw pointers from std::vector
我有以下模式:
- 我有一个
std::vector
包含指向对象的原始指针(我知道原始指针是 "evil",但它是需要维护的遗留软件)。
- 现在我需要为向量中的每个元素做一个测试,如果测试是肯定的,对指针做一些事情,删除它,然后从向量中删除它:
伪代码:
for each pointer in vector
{
if (SomeTest(pointer))
{
DoSomething(pointer)
delete pointer
remove pointer from vector
}
}
我无法为此想出一些漂亮干净的代码。
This link 提供了不同的方法,但它们对我来说都或多或少有些麻烦。
我现在用的比较麻烦的解决方案:
for(auto & p : v)
{
if (SomeTest(p))
{
DoSomething(p);
delete p;
p = nullptr;
}
}
v.erase(std::remove(v.begin(), v.end(), nullptr), v.end());
答案通常是:了解您的 <algorithm>
(这对我自己来说是一个很好的提醒);)
std::partition
是您要查找的内容:std::partition(begin, end, p)
"moves" [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's 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));
}
更进一步
此实现有两个主要缺点:向量的顺序不稳定 (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);
假设你有一个整型指针向量。这是我的解决方案:
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++;
}
}
我会使用的一些方法:
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());
最简单的解决方案 - 从链接的文章开始 - 是采用 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
,我猜想这是否有同样的问题。
您可以使用 std::remove_if
我不确定为什么您链接的文章使用 std::remove_if
before 删除指针,因为那样行不通。您必须删除 指针,然后 删除:
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
的标准保证谓词对每个元素只调用一次。
使用下面的方法,首先拆分要删除的元素,然后删除,然后调整向量。
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));
提供的谓词必须为真,元素才能继续工作,因为不符合谓词的元素在最后一个范围内。
保持简单。亚格尼。没有理由去解决一个更通用和更复杂的问题版本,因为你可能会需要它(提示:你不会),或者寻找晦涩的 STL 方法(好吧,除非你 wish 到).
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);
我有以下模式:
- 我有一个
std::vector
包含指向对象的原始指针(我知道原始指针是 "evil",但它是需要维护的遗留软件)。 - 现在我需要为向量中的每个元素做一个测试,如果测试是肯定的,对指针做一些事情,删除它,然后从向量中删除它:
伪代码:
for each pointer in vector
{
if (SomeTest(pointer))
{
DoSomething(pointer)
delete pointer
remove pointer from vector
}
}
我无法为此想出一些漂亮干净的代码。
This link 提供了不同的方法,但它们对我来说都或多或少有些麻烦。
我现在用的比较麻烦的解决方案:
for(auto & p : v)
{
if (SomeTest(p))
{
DoSomething(p);
delete p;
p = nullptr;
}
}
v.erase(std::remove(v.begin(), v.end(), nullptr), v.end());
答案通常是:了解您的 <algorithm>
(这对我自己来说是一个很好的提醒);)
std::partition
是您要查找的内容:std::partition(begin, end, p)
"moves" [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's 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));
}
更进一步
此实现有两个主要缺点:向量的顺序不稳定 (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);
假设你有一个整型指针向量。这是我的解决方案:
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++;
}
}
我会使用的一些方法:
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());
最简单的解决方案 - 从链接的文章开始 - 是采用 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
,我猜想这是否有同样的问题。
您可以使用 std::remove_if
我不确定为什么您链接的文章使用 std::remove_if
before 删除指针,因为那样行不通。您必须删除 指针,然后 删除:
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
的标准保证谓词对每个元素只调用一次。
使用下面的方法,首先拆分要删除的元素,然后删除,然后调整向量。
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));
提供的谓词必须为真,元素才能继续工作,因为不符合谓词的元素在最后一个范围内。
保持简单。亚格尼。没有理由去解决一个更通用和更复杂的问题版本,因为你可能会需要它(提示:你不会),或者寻找晦涩的 STL 方法(好吧,除非你 wish 到).
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);