【问题标题】:why not implement c++ std::vector::pop_front() by shifting the pointer to vector[0]?为什么不通过将指针移到vector[0]来实现c++ std::vector::pop_front()?
【发布时间】:2012-02-22 21:25:29
【问题描述】:

为什么不能通过简单地将向量名称中包含的指针移一位来为 C++ 向量实现pop_front()?因此,在包含数组foo 的向量中,foo 是指向foo[0] 的指针,因此pop_front() 将使指针变为foo = foo[1],而括号运算符将只进行正常的指针数学运算。这与 C++ 在为数组分配空间时如何跟踪您正在使用的内存有关吗?

这与我看到的其他问题类似,即为什么 std::vector 没有 pop_front() 函数,我承认,但我没有人问你为什么不能移动指针。

【问题讨论】:

  • 您可以将deque 用作更通用、更灵活的容器,本质上可以直接替代vector。甚至有人可能会说 deque 应该是 C++ 中的“默认”容器,而 vector 应该保留用于连续存储的特殊需要。
  • @KerrekSB:deque 不是双向链表吗?如果是这样,它绝不应该是默认容器。
  • @VioletGiraffe:重点是“如果是这样”。幸运的是,我们在这里的“else”分支:-)
  • @VioletGiraffe:不,不是。如果是这种情况,我们就不会有 deque,因为 std::list<> 已经是双重链接的。
  • 对我之前的评论的澄清:双向链表可以用于队列本身,但整个容器不能是双向链表,因为它不支持恒定时间元素访问。并且 +1 给 OP 一个好问题!

标签: c++ arrays stl vector


【解决方案1】:

因为实现者想要优化向量的大小。他们通常使用 3 个指针,一个指向开始,一个指向容量(分配的结束),一个指向结束。

执行您需要的操作会为每个向量添加另外 4 个字节(在 c++ 程序中有很多这样的字节),但收益很小:向量的约定是在推回新元素时要快速,删除和插入是“不寻常”的操作及其性能比班级的规模更重要。

【讨论】:

  • 我不明白你的硬性主张是什么,即“性能比大小更重要”。不说谁和多久不重要,你的陈述充其量是毫无意义的。
  • 该标准仅对push_back[] 施加了性能限制。我知道的两个实现(MSVC / GCC)使用 3 个指针,因为 他们 认为大小比其他操作的性能更重要,我只是重复我一直在网上阅读的内容(尽管我承认我不会'无法轻松找到来源,我记得 Stephan T. Lavavej 在 C9 上的视频至少提到了它)。现在,当然,有些人可能比其他人更关心“前”操作,但标准人认为它不值得在向量中涵盖。还有其他课程。其他答案同意。
  • 我的主要问题是我的建议不应该向向量添加另一个指针的字节,除非(正如我对 Borealid 的回答所评论的那样)没有其他方法可以释放内存,在这种情况下,真正的原因是因为没有其他方法可以确保内存被释放。我主要是建议您将第一个指针移至向量的新“头”,希望在此过程中释放旧头。这帮助我了解了更多向量是如何实现的,所以谢谢!
【解决方案2】:

如果这样做,vector 将无法释放其内存。

通常,您希望每个 vector 对象的开销很小。这意味着您只存储三项:指向第一个元素的指针、容量和长度。

为了实现您的建议,每个vector所有)都需要一个额外的成员变量:从第零个元素所在的起始指针的偏移量。否则,无法释放内存,因为它的原始句柄会丢失。

这是一种折衷,但一般来说,一个可能有数百万个实例的对象的内存消耗比对 @ 执行性能方面绝对最糟糕的事情更有价值987654324@.

【讨论】:

  • 谢谢,这正是我想要的。有没有办法只在你遗漏的内存上调用删除/释放?例如,您将指向元素 1 的指针存储在临时变量中,将向量的指针移至元素 2,然后删除元素 1?我知道 delete 肯定会区分单个元素和块 (delete [] array),尽管这是用于动态内存。
  • @IanOoi 这种分配策略确实效率低下。为了单独释放,分配必须单独跟踪 - 簿记开销,无论是浪费的 RAM 还是新分配的时间,都会很快变坏。
【解决方案3】:

你可以,但它会使实现有点复杂,并添加一个指向类型大小的开销指针(因此它可以跟踪实际分配的地址)。这值得吗?有时。首先考虑其他可以更好地处理您的使用的结构(可能是双端队列?)。

【讨论】:

    【解决方案4】:

    可以这样做,但vector 被设计成一个简单的容器,具有恒定时间索引查找和从末尾推送/弹出。执行您的建议会使实现复杂化,因为它必须跟踪分配的开始和“当前”开始。更不用说你仍然不能保证在前面插入恒定的时间,但有时你可能会得到它。

    如果您需要一个具有恒定时间前后插入和移除的容器,这正是deque 的用途,无需修改vector 来处理它。

    【讨论】:

    • 这主要是一种好奇心,因为向量是围绕数组构建的(如果我弄错了,请纠正我),如果不插入,你仍然可以保证恒定的时间 removal,这就是我主要是问。我在另一条评论中说过,即使你做了指针 finagling 我不认为 push_front 甚至可以保证如果向量“之前”的块已经分配给其他东西,这意味着你要么有做愚蠢的转变或放弃。如果有 pop_front 但没有 push_front,一致性仍然是个问题。
    【解决方案5】:

    您的建议的另一个缺点是您将浪费内存空间,因为您无法在移位后使用数组左侧的空间。执行pop_front() 的次数越多,直到向量被破坏为止,您将浪费的越多。

    【讨论】:

    • 可以想象,稍后调整大小可能会利用该空间。
    • 这是一个非常好的观点,我认为 Borealid 是正确的,如果有人做了这样的实现,那么它不确定析构函数是否可以释放内存,因为你已经遗漏了大块数据没有指针并且不能被释放。您可以尝试在将名称移动到下一个索引并在单个空间而不是整个块上调用 delete/free 的位置使用它,但这可能会使其仍然生气。
    【解决方案6】:

    我开始输入详细的答案来解释内存是如何分配和释放的,但在全部输入之后,我意识到仅凭内存问题并不能证明为什么 pop_front 不存在,因为这里建议的其他答案。

    在大多数情况下,在额外成本是另一个指针的向量中包含pop_front 是合理的。在我看来,问题是push_front。如果容器有pop_front,那么它也应该有push_front,否则容器不一致。 push_front 对于矢量容器来说绝对是昂贵的(除非您将 pushes 与您的 pops 匹配,这不是一个好的设计)。没有push_front,如果一个人在没有push_front 功能的情况下执行大量pop_front 操作,那么向量真的 会浪费内存。

    现在需要pop_frontpush_front 需要一个类似于向量(恒定时间随机访问)的容器,这就是deque 的原因。

    【讨论】:

    • 好的,我正在和我的室友讨论 pop_front 应该如何工作(它不应该是一个额外的指针,因为数组是如何实现的)以及 push_front 不会因为你可以使用类似的方案作为 pop_front 但没有办法验证您添加的额外插槽将是免费的并且不会分配给其他东西而不做很多魔术。这是有道理的,但似乎还是有点奇怪。 deque 也是恒定的随机访问吗?心都碎了。我会调查的。
    • @IanOoi:“我正在和我的室友讨论 pop_front 应该如何工作(由于数组的实现方式,它不应该是一个额外的指针)”除非你在原始内存地址上调用free(),移动指针会丢失该地址。简而言之,您需要一个额外的指针来跟踪原始内存地址(或者一个存储pop_front() 被调用次数的变量,它占用与 32 上的指针相同的内存量位系统)。
    【解决方案7】:

    您可以使用std::deque 代替std::vector。它是一个双端队列,也有类似向量的访问成员。它实现了前推和后推/弹出。

    http://www.cplusplus.com/reference/stl/deque/

    【讨论】:

      猜你喜欢
      • 2015-12-15
      • 2012-03-15
      • 1970-01-01
      • 1970-01-01
      • 2016-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-01
      相关资源
      最近更新 更多