std :: remove和erase之间的区别?

时间:2013-10-10 13:21:47

标签: c++ vector stl

我怀疑我想澄清一下。我知道std::vectorerase之间std::remove的不同行为,其中第一个从向量中物理移除元素,减小大小,另一个只移动一个元素,留下容量相同。

这是出于效率原因吗?使用erasestd::vector中的所有元素都会移动1,从而导致大量副本; std::remove只进行“逻辑”删除,并通过移动来保持向量不变。如果物体很重,这种差异可能很重要,对吗?

8 个答案:

答案 0 :(得分:30)

  

这只是出于效率原因吗?通过使用擦除,std :: vector中的所有元素将被移位1,从而导致大量复制; std :: remove只是一个'逻辑'删除,并通过移动东西保持向量不变。如果物体很重,那么差异很重要,对吗?

使用这个习语的原因正是如此。性能有一个好处,但不是单擦除的情况。重要的是你需要从向量中删除多个元素。在这种情况下,std::remove会将每个未删除的元素仅复制一次到最终位置,而vector::erase方法会将所有元素从位置移动到最终位置多次。考虑:

std::vector<int> v{ 1, 2, 3, 4, 5 };
// remove all elements < 5

如果你逐个删除了向量移除元素,你将删除1,导致被移位的余数元素的副本(4)。然后你将删除2并将所有剩余元素移动一(3)...如果你看到模式这是一个O(N^2)算法。

std::remove的情况下,算法维护读写头,并迭代容器。对于前4个元素,将移动读取头并测试元素,但不复制任何元素。仅对于第五个元素,对象将从最后一个位置复制到第一个位置,算法将使用单个副本完成,并将迭代器返回到第二个位置。这是O(N)算法。带有范围的后面的std::vector::erase将导致所有剩余元素的破坏并调整容器的大小。

正如其他人所提到的,标准库中的算法应用于迭代器,并且缺乏对迭代序列的了解。这种设计比算法知道容器的其他方法更灵活,因为算法的单个实现可以与符合迭代器要求的任何序列一起使用。例如,考虑std::remove_copy_if,即使没有容器也可以使用它,使用生成/接受序列的迭代器:

std::remove_copy_if(std::istream_iterator<int>(std::cin),
                    std::istream_iterator<int>(),
                    std::ostream_iterator<int>(std::cout, " "),
                    [](int x) { return !(x%2); } // is even
                    );

这一行代码将从标准输入中过滤掉所有偶数并将其转储到标准输出,而不需要将所有数字加载到容器的内存中。这是拆分的优点,缺点是算法不能修改容器本身,只能通过迭代器引用的值。

答案 1 :(得分:8)

std::remove是来自STL的算法,它与容器无关。它需要一些概念,但它的设计也适用于C数组,这些数组的大小是静态的。

答案 2 :(得分:6)

std::remove只返回一个新的end()迭代器,指向最后一个未删除元素之后的一个(从返回值到end()的项目数将与项目数相匹配)要删除,但不能保证它们的值与您删除的值相同 - 它们处于有效但未指定的状态)。这样做可以使它适用于多种容器类型(基本上是ForwardIterator可以迭代的任何容器类型。)

std::vector::erase实际上在调整大小后设置了新的end()迭代器。这是因为vector的方法实际上知道如何处理调整它的迭代器(std::list::erasestd::deque::erase等也可以这样做。)

remove组织一个给定的容器来删除不需要的对象。容器的擦除功能实际上处理“移除”容器需要它完成的方式。这就是他们分开的原因。

答案 3 :(得分:5)

我认为这与需要直接访问矢量本身以便能够调整它有关。 std :: remove只能访问迭代器,所以它无法告诉向量“嘿,你现在有更少的元素”。

请参阅yves Baumes回答为什么std :: remove是这样设计的。

答案 4 :(得分:4)

是的,这就是它的要点。请注意,其性能特征不同的其他标准容器也支持erase(例如list::erase为O(1)),而std::remove与容器无关,适用于任何类型是forward iterator的(因此它适用于例如裸阵列)。

答案 5 :(得分:0)

有点儿。算法,例如删除迭代器上的工作(它是表示集合中元素的抽象),它们不一定知道它们正在操作哪种类型的集合 - 因此无法调用集合上的成员来进行实际删除。

这很好,因为它允许算法在任何容器上以及整个集合的子集范围内一般地工作。

另外,正如您所说,对于性能 - 如果您只需访问逻辑结束位置以传递给另一个算法,则可能没有必要实际删除(并销毁)元素。

答案 6 :(得分:0)

标准库算法在序列上运行。序列由一对迭代器定义;第一个元素在序列的第一个元素,第二个点在序列的一个接一个的末尾。就这样;算法并不关心序列的来源。

标准库容器包含数据值,并提供一对迭代器,用于指定算法使用的序列。它们还提供了成员函数,通过利用容器的内部数据结构,可以更有效地执行与算法相同的操作。

答案 7 :(得分:0)

尝试使用以下代码以便更好地理解。

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
const auto newend (remove(begin(v), end(v), 2));

for(auto a : v){
    cout << a << " ";
}
cout << endl;
v.erase(newend, end(v));
for(auto a : v){
    cout << a << " ";
}