shrink_to_fit是减少`std :: vector`到其大小的容量的正确方法吗?

101*_*010 24 c++ stl vector c++11

在C++ 11 shrink_to_fit是引入补充某些STL容器(例如,std::vector,std::deque,std::string).

Synopsizing,其主要功能是请求与之关联的容器,以减少其容量以适应其大小.但是,此请求是非绑定的,并且容器实现可以自由地进行优化,并使向量的容量大于其大小.

此外,在之前的SO问题中,OP不鼓励使用它shrink_to_fit来减小其容量std::vector.不这样做的理由如下:

shrink_to_fit什么都不做,或者它给你缓存局部性问题,并且执行O(n)(因为你必须将每个项目复制到他们新的较小的家庭).通常,将松弛留在内存中会更便宜.@Massa

有人可以如此善意地解决以下问题:

  • 报价中的参数是否成立?
  • 如果是的话,将STL容器的容量缩小到适当大小的正确方法什么(至少对于std::vector).
  • 如果有更好的方法来缩小容器,那么后续存在的原因是什么shrink_to_fit

Dav*_*eas 15

报价中的参数是否成立?

措施,你会知道.你是否受限于记忆?你能预先确定正确的尺寸吗?事后收缩reserve收缩更有效率.总的来说,我倾向于同意这样的前提,即大多数用途可能都很好.

如果是,那么将STL容器的容量缩小到其大小的正确方法是什么(至少对于std :: vector).

评论不仅适用于shrink_to_fit,而且适用于任何其他缩小方式.鉴于你不能realloc到位,它涉及获取不同的内存块并在那里进行复制,无论你使用什么机制进行收缩.

如果有一个更好的方法来缩小容器,那么后来存在shrink_to_fit的原因是什么?

该请求不具有约束力,但替代方案没有更好的保证.问题是缩小是否有意义,如果确实如此,那么提供一个shrink_to_fit可以利用对象被移动到新位置这一事实的操作是有意义的.即如果类型T具有noexcept(true)移动构造函数,它将分配新内存并移动元素.

虽然您可以在外部实现相同的功能,但此界面可简化操作.与shrink_to_fitC++ 03相同的是:

std::vector<T>(current).swap(current);
Run Code Online (Sandbox Code Playgroud)

但是这种方法的问题在于,当复制完成到临时时它不知道current将要被替换,没有什么能告诉库它可以移动被保持的对象.请注意,使用std::move(current)不会达到预期的效果,因为它会移动整个缓冲区,保持相同capacity().

从外部实现这将更麻烦:

{
   std::vector<T> copy;
   if (noexcept(T(std::move(declval<T>())))) {
      copy.assign(std::make_move_iterator(current.begin()),
                  std::make_move_iterator(current.end()));
   } else {
      copy.assign(current.begin(), current.end());
   }
   copy.swap(current);
}
Run Code Online (Sandbox Code Playgroud)

假设我得到if条件正确...这可能不是你想要在每次想要这个操作时写的东西.

  • @JonasWielicki:除了 C++ 内存分配器不使用“realloc”。boost 内部的概念验证正在进行一些工作,但是分配器*不能*使用 `realloc` (或者更确切地说,它们可以使用 `realloc` 但只能实现 `malloc` 和 `free`) (2认同)
  • 是否有任何实现忽略shrink_to_fit() 请求?主要的似乎尊重它。 (2认同)

Mas*_*ssa 14

  • 论证会持有吗?

由于争论最初是我的,所以不要介意我一个接一个地为他们辩护:

  1. 要么shrink_to_fit什么都不做(......)

    正如所提到的那样,标准说(很多次,但在vector第23.3.7.3节的情况下......)请求是非绑定的,以允许优化的实现范围.这意味着实现可以定义shrink_to_fit为无操作.

  2. (...)或它给你缓存局部性问题

    在这种情况下shrink_to_fit作为无操作实现的,你必须分配容量新的基础容器size(),副本(或,在最好的情况下,移动)构建所有N = size()从旧的新项目,销毁所有旧的(在移动的情况下,这应该被优化,但是这可能涉及在旧容器上再次循环)然后破坏旧容器本身.libstdc++-4.9正如David Rodriguez所描述的那样,这完成了

          _Tp(__make_move_if_noexcept_iterator(__c.begin()),
              __make_move_if_noexcept_iterator(__c.end()),
              __c.get_allocator()).swap(__c);
    
    Run Code Online (Sandbox Code Playgroud)

    并且libc++-3.5通过其中的函数__alloc_traits大致相同.

    哦,并且一个实现绝对不能依赖realloc(即使它malloc在内部::operator new使用其内存分配),因为realloc,如果它不能就地收缩,将要么单独留下内存(无操作情况)或进行按位复制(并且错过)重新调整正确的C++复制/移动构造函数会给出的指针等的机会.

    当然,可以编写一个可收缩的内存分配器,并在其向量的构造函数中使用它.

    在向量大于高速缓存行的简单情况下,所有这些移动都会对高速缓存施加压力.

  3. 它是O(n)

    如果n = size(),我认为它已经建立在上面,至少,你必须做一个n大小的分配,n复制或移动结构,n破坏和一个old_capacity大小的释放.

  4. 通常只是将松弛留在记忆中更便宜

    显然,除非你真的要求免费内存(在这种情况下,将数据保存到磁盘并在以后按需重新加载它可能更明智......)

  • 如果是,那么将STL容器的容量缩小到其大小的正确方法是什么(至少对于std :: vector).

正确的方法仍然是shrink_to_fit......你必须要么不依赖它,要么非常了解你的实施!

  • 如果有一个更好的方法来缩小容器,那么后来存在shrink_to_fit的原因是什么?

没有更好的方法,但存在的原因shrink_to_fit是,AFAICT,有时你的程序可能会感到记忆压力,这是一种治疗它的方法.不是很好的方式,但仍然.

HTH!