简体   繁体   English

向量与列表从stl-删除方法

[英]vector vs. list from stl - remove method

std::list has a remove method, while the std::vector doesn't. std::list具有remove方法,而std::vector没有。 What is the reason for that? 是什么原因呢?

std::list<>::remove is a physical removal method, which can be implemented by physically destroying list elements that satisfy certain criteria (by physical destruction I mean the end of element's storage duration). std::list<>::remove是一种物理删除方法,可以通过物理破坏满足某些条件的列表元素来实现(通过物理破坏,我指的是元素存储持续时间的结尾)。 Physical removal is only applicable to lists. 物理删除仅适用于列表。 It cannot be applied to arrays, like std::vector<> . 它不能应用于数组,例如std::vector<> It simply is not possible to physically end storage duration of an individual element of an array. 根本不可能物理上结束阵列中单个元素的存储时间。 Arrays can only be created and destroyed as a whole. 数组只能整体创建和销毁。 This is why std::vector<> does not have anything similar to std::list<>::remove . 这就是为什么std::vector<>没有类似于std::list<>::remove

The universal removal method applicable to all modifiable sequences is what one might call logical removal: the target elements are "removed" from the sequence by overwriting their values with values of elements located further down in the sequence. 适用于所有可修改序列的通用删除方法就是所谓的逻辑删除:通过用位于序列中位于最下方的元素的值覆盖目标元素的值,从而从序列中“删除”目标元素。 Ie the sequence is shifted and compacted by copying the persistent data "to the left". 即,通过将“持久数据”复制到“左侧”来移动和压缩序列。 Such logical removal is implemented by freestanding functions, like std::remove . 这种逻辑删除是通过独立功能(如std::remove Such functions are applicable in equal degree to both std::vector<> and std::list<> . 这样的函数在std::vector<>std::list<>上都具有同等的适用性。

In cases where the approach based on immediate physical removal of specific elements applies, it will work more efficiently than the generic approach I referred above as logical removal. 如果采用了基于对特定元素进行立即物理移除的方法,则该方法将比我上面所说的逻辑移除的通用方法更有效。 That is why it was worth providing it specifically for std::list<> . 这就是为什么值得为std::list<>提供它。

std::list::remove removes all items in a list that match the provided value. std::list::remove删除列表中与提供的值匹配的所有项目。

std::list<int> myList;
// fill it with numbers
myList.remove(10); // physically removes all instances of 10 from the list

It has a similar function, std::list::remove_if , which allows you to specify some other predicate. 它具有类似的功能std::list::remove_if ,它允许您指定其他谓词。

std::list::remove (which physically removes the elements) is required to be a member function as it needs to know about the memory structure (that is, it must update the previous and next pointers for each item that needs to be updated, and remove the items), and the entire function is done in linear time (a single iteration of the list can remove all of the requested elements without invalidating any of the iterators pointing to items that remain). std::list::remove (从物理上删除元素)必须是成员函数,因为它需要了解内存结构(也就是说,它必须为每个需要更新的项目更新前一个和下一个指针,然后删除项目),整个功能将在线性时间内完成(列表的单次迭代可以删除所有请求的元素,而不会导致任何指向剩余项目的迭代器无效)。

You cannot physically remove a single element from a std::vector . 您不能从std::vector物理删除单个元素。 You either reallocate the entire vector, or you move every element after the removed items and adjust the size member. 您要么重新分配整个向量,要么将每个元素移到删除的项目之后并调整size成员。 The "cleanest" implementation of that set of operations would be to do 这组操作的“最干净”的实现是

// within some instance of vector
void vector::remove(const T& t)
{
    erase(std::remove(t), end());
}

Which would require std::vector to depend on <algorithm> (something that is currently not required). 这将要求std::vector依赖于<algorithm> (目前不需要)。

As the "sorting" is needed to remove the items without multiple allocations and copies being required. 由于需要“分类”才能删除项目,而无需进行多次分配和复制。 (You do not need to sort a list to physically remove elements). (您无需对列表进行排序即可物理删除元素)。

Contrary to what others are saying, it has nothing to do with speed. 与其他人所说的相反,它与速度无关。 It has to do with the algorithm needing to know how the data is stored in memory. 它与需要知道如何将数据存储在内存中的算法有关。

As a side note: This is also a similar reason why std::remove (and friends) do not actually remove the items from the container they operate on; 附带说明:这也是std::remove (和朋友)实际上未从其操作的容器中删除项目的类似原因; they just move all the ones that are not going to be removed to the "front" of the container. 他们只是将所有不会被移除的容器移动到容器的“前端”。 Without the knowledge of how to actually remove an object from a container, the generic algorithm cannot actually do the removing. 如果不知道如何从容器中实际删除对象,则通用算法实际上无法执行删除操作。

Consider the implementation details of both containers. 考虑两个容器的实现细节。 A vector has to provide a continuous memory block for storage. vector必须提供连续的存储块进行存储。 In order to remove an element at index n != N (with N being the vector's length), all elements from n+1 to N-1 need to be moved. 为了删除索引为n != N的元素(其中N为向量的长度),需要移动从n+1N-1所有元素。 The various functions in the <algorithm> header implement that behavior, like std::remove or std::remove_if . <algorithm>标头中的各种函数实现了该行为,例如std::removestd::remove_if The advantage of these being free-standing functions is that they can work for any type that offers the needed iterators. 这些是独立功能的优点是它们可以用于提供所需迭代器的任何类型。

A list on the other hand, is implemented as a linked list structure, so: 另一方面, list是作为链接列表结构实现的,因此:

  • It's fast to remove an element from anywhere 从任何地方快速删除元素
  • It's impossible to do it as efficiently using iterators (since the internal structure has to be known and manipulated). 使用迭代器无法高效地做到这一点(因为必须知道和操纵内部结构)。

In general in STL the logic is "if it can be done efficiently - then it's a class member. If it's inefficient - then it's an outside function" 通常,在STL中,逻辑是“如果可以高效完成-则它是一个类成员。如果效率低下-则它是外部函数”

This way they make the distinction between "correct" (ie "efficient") use of classes vs. "incorrect" (inefficient) use. 这样,他们就可以区分“正确”(即“有效”)使用类和“不正确”(无效)使用类。

For example, random access iterators have a += operator, while other iterators use the std::advance function. 例如,随机访问迭代器具有+ =运算符,而其他迭代器使用std::advance函数。

And in this case - removing elements from an std::list is very efficient as you don't need to move the remaining values like you do in std::vector 在这种情况下,从std::list删除元素非常有效,因为您无需像在std::vector那样移动其余值

It's all about efficiency AND reference/pointer/iterator validity. 这全都与效率和参考/指针/迭代器的有效性有关。 list items can be removed without disturbing any other pointers and iterators. 可以删除list项,而不会干扰任何其他指针和迭代器。 This is not true for a vector and other containers in all but the most trivial cases. 除了最琐碎的情况之外,对于vector和其他容器而言,情况并非如此。 Nothing prevents use the external strategy, but you have a superior options.. That said this fellow said it better than I could on a duplicate question 没有什么可以阻止使用外部策略的,但是您有一个更好的选择。

From another poster on a duplicate question: 从另一个海报上重复的问题:

The question is not why std::vector does not offer the operation, but rather why does std::list offer it. 问题不是为什么std :: vector不提供操作,而是为什么std :: list提供了操作。 The design of the STL is focused on the separation of the containers and the algorithms by means of iterators, and in all cases where an algorithm can be implemented efficiently in terms of iterators, that is the option. STL的设计侧重于通过迭代器将容器和算法分离,并且在所有可以根据迭代器有效地实现算法的情况下,都是可以的选择。

There are, however, cases where there are specific operations that can be implemented much more efficiently with knowledge of the container. 但是,在某些情况下,可以通过了解容器来更有效地实施特定操作。 That is the case of removing elements from a container. 这就是从容器中删除元素的情况。 The cost of using the remove-erase idiom is linear in the size of the container (which cannot be reduced much), but that hides the fact that in the worst case all but one of those operations are copies of the objects (the only element that matches is the first), and those copies can represent quite a big hidden cost. 使用remove-erase习惯用法的成本在容器的大小上是线性的(不能减少很多),但这掩盖了一个事实,在最坏的情况下,除了其中一个操作之外,所有操作都是对象的副本(唯一的元素匹配项是第一个),而这些副本可能代表了很大的隐藏成本。

By implementing the operation as a method in std::list the complexity of the operation will still be linear, but the associated cost for each one of the elements removed is very low, a couple of pointer copies and releasing of a node in memory. 通过将操作作为std :: list中的一种方法来实现,操作的复杂度仍然是线性的,但是删除的每个元素的相关成本都非常低,有几个指针复制并释放了内存中的节点。 At the same time, the implementation as part of the list can offer stronger guarantees: pointers, references and iterators to elements that are not erased do not become invalidated in the operation. 同时,作为列表一部分的实现可以提供更强的保证:未擦除元素的指针,引用和迭代器不会在操作中失效。

Another example of an algorithm that is implemented in the specific container is std::list::sort, that uses mergesort that is less efficient than std::sort but does not require random-access iterators. 在特定容器中实现的算法的另一个示例是std :: list :: sort,它使用的合并排序比std :: sort效率低,但不需要随机访问迭代器。

So basically, algorithms are implemented as free functions with iterators unless there is a strong reason to provide a particular implementation in a concrete container. 因此,基本上,除非有充分的理由在具体的容器中提供特定的实现,否则算法将作为带有迭代器的自由函数实现。

std::list is designed to work like a linked list. std::list设计为像链表一样工作。 That is, it is designed (you might say optimized) for constant time insertion and removal ... but access is relatively slow (as it typically requires traversing the list). 也就是说,它被设计为(可以说是优化的)用于恒定时间的插入和移除...但是访问相对较慢(因为它通常需要遍历列表)。

std::vector is designed for constant-time access, like an array. std::vector设计用于恒定时间访问,就像数组一样。 So it is optimized for random access ... but insertion and removal are really only supposed to be done at the "tail" or "end", elsewhere they're typically going to be much slower. 因此,它针对随机访问进行了优化……但是插入和删除实际上只应该在“尾巴”或“末端”完成,而在其他地方通常会慢得多。

Different data structures with different purposes ... therefore different operations. 具有不同用途的不同数据结构...因此具有不同的操作。

To remove an element from a container you have to find it first. 要从容器中删除元素,必须先找到它。 There's a big difference between sorted and unsorted vectors, so in general, it's not possible to implement an efficient remove method for the vector. 排序向量和未排序向量之间有很大的区别,因此,一般来说,不可能对向量实施有效的删除方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM