繁体   English   中英

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

[英]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+1N-1所有元素。 <algorithm>标头中的各种函数实现了该行为,例如std::removestd::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.

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