[英]vector vs. list from stl - remove method
std::list
具有remove
方法,而std::vector
没有。 是什么原因呢?
std::list<>::remove
是一种物理删除方法,可以通过物理破坏满足某些条件的列表元素来实现(通过物理破坏,我指的是元素存储持续时间的结尾)。 物理删除仅适用于列表。 它不能应用于数组,例如std::vector<>
。 根本不可能物理上结束阵列中单个元素的存储时间。 数组只能整体创建和销毁。 这就是为什么std::vector<>
没有类似于std::list<>::remove
。
适用于所有可修改序列的通用删除方法就是所谓的逻辑删除:通过用位于序列中位于最下方的元素的值覆盖目标元素的值,从而从序列中“删除”目标元素。 即,通过将“持久数据”复制到“左侧”来移动和压缩序列。 这种逻辑删除是通过独立功能(如std::remove
。 这样的函数在std::vector<>
和std::list<>
上都具有同等的适用性。
如果采用了基于对特定元素进行立即物理移除的方法,则该方法将比我上面所说的逻辑移除的通用方法更有效。 这就是为什么值得为std::list<>
提供它。
std::list::remove
删除列表中与提供的值匹配的所有项目。
std::list<int> myList;
// fill it with numbers
myList.remove(10); // physically removes all instances of 10 from the list
它具有类似的功能std::list::remove_if
,它允许您指定其他谓词。
std::list::remove
(从物理上删除元素)必须是成员函数,因为它需要了解内存结构(也就是说,它必须为每个需要更新的项目更新前一个和下一个指针,然后删除项目),整个功能将在线性时间内完成(列表的单次迭代可以删除所有请求的元素,而不会导致任何指向剩余项目的迭代器无效)。
您不能从std::vector
物理删除单个元素。 您要么重新分配整个向量,要么将每个元素移到删除的项目之后并调整size
成员。 这组操作的“最干净”的实现是
// within some instance of vector
void vector::remove(const T& t)
{
erase(std::remove(t), end());
}
这将要求std::vector
依赖于<algorithm>
(目前不需要)。
由于需要“分类”才能删除项目,而无需进行多次分配和复制。 (您无需对列表进行排序即可物理删除元素)。
与其他人所说的相反,它与速度无关。 它与需要知道如何将数据存储在内存中的算法有关。
附带说明:这也是std::remove
(和朋友)实际上未从其操作的容器中删除项目的类似原因; 他们只是将所有不会被移除的容器移动到容器的“前端”。 如果不知道如何从容器中实际删除对象,则通用算法实际上无法执行删除操作。
考虑两个容器的实现细节。 vector
必须提供连续的存储块进行存储。 为了删除索引为n != N
的元素(其中N
为向量的长度),需要移动从n+1
到N-1
所有元素。 <algorithm>
标头中的各种函数实现了该行为,例如std::remove
或std::remove_if
。 这些是独立功能的优点是它们可以用于提供所需迭代器的任何类型。
另一方面, list
是作为链接列表结构实现的,因此:
通常,在STL中,逻辑是“如果可以高效完成-则它是一个类成员。如果效率低下-则它是外部函数”
这样,他们就可以区分“正确”(即“有效”)使用类和“不正确”(无效)使用类。
例如,随机访问迭代器具有+ =运算符,而其他迭代器使用std::advance
函数。
在这种情况下,从std::list
删除元素非常有效,因为您无需像在std::vector
那样移动其余值
这全都与效率和参考/指针/迭代器的有效性有关。 可以删除list
项,而不会干扰任何其他指针和迭代器。 除了最琐碎的情况之外,对于vector
和其他容器而言,情况并非如此。 没有什么可以阻止使用外部策略的,但是您有一个更好的选择。
从另一个海报上重复的问题:
问题不是为什么std :: vector不提供操作,而是为什么std :: list提供了操作。 STL的设计侧重于通过迭代器将容器和算法分离,并且在所有可以根据迭代器有效地实现算法的情况下,都是可以的选择。
但是,在某些情况下,可以通过了解容器来更有效地实施特定操作。 这就是从容器中删除元素的情况。 使用remove-erase习惯用法的成本在容器的大小上是线性的(不能减少很多),但这掩盖了一个事实,在最坏的情况下,除了其中一个操作之外,所有操作都是对象的副本(唯一的元素匹配项是第一个),而这些副本可能代表了很大的隐藏成本。
通过将操作作为std :: list中的一种方法来实现,操作的复杂度仍然是线性的,但是删除的每个元素的相关成本都非常低,有几个指针复制并释放了内存中的节点。 同时,作为列表一部分的实现可以提供更强的保证:未擦除元素的指针,引用和迭代器不会在操作中失效。
在特定容器中实现的算法的另一个示例是std :: list :: sort,它使用的合并排序比std :: sort效率低,但不需要随机访问迭代器。
因此,基本上,除非有充分的理由在具体的容器中提供特定的实现,否则算法将作为带有迭代器的自由函数实现。
std::list
设计为像链表一样工作。 也就是说,它被设计为(可以说是优化的)用于恒定时间的插入和移除...但是访问相对较慢(因为它通常需要遍历列表)。
std::vector
设计用于恒定时间访问,就像数组一样。 因此,它针对随机访问进行了优化……但是插入和删除实际上只应该在“尾巴”或“末端”完成,而在其他地方通常会慢得多。
具有不同用途的不同数据结构...因此具有不同的操作。
要从容器中删除元素,必须先找到它。 排序向量和未排序向量之间有很大的区别,因此,一般来说,不可能对向量实施有效的删除方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.