从任意容器中廉价删除元素的惯用方法?

Idiomatic way to cheaply remove an element from an arbitrary container?

本文关键字:方法 元素 删除 任意容      更新时间:2023-10-16

所有C++标准库容器都有一个insert()方法;但它们并不都有remove()方法,该方法不接受任何参数,而是以任意顺序执行最便宜的删除。现在,当然,对于不同的容器,这将有不同的作用:在向量中,我们将从后面删除,在单个列表中,我们将从前面删除(除非我们保留指向尾部的指针(,等等,根据实现细节。

那么,除了为每个容器滚动我自己的模板专用化之外,还有没有更惯用的方法可以做到这一点?

标准容器设计的一部分是,如果可以为该容器提供最佳操作(通过选定的度量(,则它们仅提供作为成员函数的操作。

如果容器提供成员函数,那是因为有某种方法可以实现该函数,这是该容器的最佳方法。

如果无法提供操作的最佳实现(如remove()(,则不提供。

只有std::list和(C++11 及更高版本(std::forward_list设计用于有效移除元素,这就是为什么它们是唯一具有remove()构件功能的容器。

其他容器不是为有效去除任意元素而设计的;

  • std::array无法调整大小,因此具有insert()remove()成员函数是没有意义的。
  • std::deque仅在开头或结尾针对移除进行了优化。
  • std::vector中移除元素的效率低于其他容器,除了(可能(从末端移除。

因此,为这些容器实现remove()成员函数违背了设计理念。

因此,如果您希望能够有效地从容器中删除元素,则需要为作业选择合适的容器。

为标准容器滚动自己的包装器来模拟某些容器不支持的操作只是误导 - 从某种意义上说,鼓励包装器类的用户相信,如果他们对性能或内存使用有特殊要求,他们不需要小心选择容器。

所以回答你的问题

"那么,除了为每个容器滚动我自己的模板专用化之外,还有没有更惯用的方法可以做到这一点?

有很多方法可以删除

  1. 序列容器和无序容器的 erase(( 返回下一个 删除项目之后的迭代器。

  2. 关联容器的 erase(( 不返回任何内容。

/* *从矢量或德克中删除*/

vector<int> vec = {1, 4, 1, 1, 1, 12, 18, 16}; // To remove all '1'
for (vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr) {
if ( *itr == 1 ) {
vec.erase(itr);
}
}   // vec: { 4, 12, 18, 16}
// Complexity: O(n*m)
remove(vec.begin(), vec.end(), 1);  // O(n) 
// vec: {4, 12, 18, 16, ?, ?, ?, ?}


vector<int>::iterator newEnd = remove(vec.begin(), vec.end(), 1);   // O(n)
vec.erase(newEnd, vec.end());  
// Similarly for algorithm: remove_if() and unique()

// vec still occupy 8 int space: vec.capacity() == 8
vec.shrink_to_fit();   // C++ 11
// Now vec.capacity() == 4 
// For C++ 03:
vector<int>(vec).swap(vec); // Release the vacant memory

/* *从列表中删除*/

list<int> mylist = {1, 4, 1, 1, 1, 12, 18, 16};
list<int>::iterator newEnd = remove(mylist.begin(), mylist.end(), 1);  
mylist.erase(newEnd, mylist.end());

mylist.remove(1);  // faster

/* *从关联容器或无序容器中删除*/

multiset<int> myset = {1, 4, 1, 1, 1, 12, 18, 16};
multiset<int>::iterator newEnd = remove(myset.begin(), myset.end(), 1);  
myset.erase(newEnd, myset.end()); // O(n)
myset.erase(1); // O(log(n)) or O(1)