适当的矢量内存管理

时间:2010-02-12 18:03:26

标签: c++ memory-management vector

我正在制作一个游戏,我有一个子弹飞来飞去。子弹完成后,我做bullets.erase(bullets.begin()+ i);然后子弹消失了。然而,它似乎没有得到记忆棒。如果我创造了5000个子弹,那么在这些子弹消失之后再创建5,000个子弹,内存保持不变,但如果我创造5000多个,而这些5000个飞行,它将分配新的空间。我该怎样做才能真正释放这段记忆?

6 个答案:

答案 0 :(得分:15)

std::vector类自动管理其内部存储器。它将扩展为容纳尽可能多的项目,但一般来说,当你删除项目时它不会自行缩小(尽管它会在它破坏时释放内存)。

std::vector有两个相关的“大小”概念。首先是“保留”大小,它是从系统分配用于存储向量元素的内存量。第二个是“已使用”大小,即向量中逻辑上有多少元素。显然,保留的大小必须至少与使用的大小一样大。您可以使用size()方法(我相信您已经知道)发现使用的大小,并且您可以使用capacity()方法发现保留的大小。

通常,当使用的和保留的大小相同,并且您尝试插入新元素时,向量将分配两倍于先前保留大小的新内部缓冲区,并将所有现有元素复制到该缓冲区中。这对您来说是透明的,除了它将使您持有的任何迭代器无效。正如我之前提到的,AFAIK,大多数STL实现永远不会缩小保留大小作为对擦除的响应。

不幸的是,虽然您可以使用reserve()方法强制向量增加其保留大小,但这对减少保留容量不起作用。据我所知,实现减少容量的最佳选择是执行以下操作:

std::vector<Bullet>(myVector).swap(myVector);

这将创建一个临时向量,它是原始向量的副本(但具有最小必要容量),然后交换两个向量的内部缓冲区。这将使您的原始矢量具有相同的数据,但可能更小的保留大小。

现在,因为创建临时副本是一项相对昂贵的操作(即它比正常的读取/插入/删除需要更多的处理器时间),所以每次擦除元素时都不希望这样做。出于同样的原因,这就是为什么当你需要超过现有的大小时,向量会将其保留的大小加倍,而不是将其增加1。因此,我建议的是,在擦除相对大量的元素之后,并且您知道不会在短时间内添加更多元素,请执行上面的交换“技巧”以减少容量。

最后,您可能还想考虑使用std::vector之外的其他内容。从许多其他类型的数据结构中删除矢量中间的元素,这似乎是你经常做的,是一个缓慢的操作(因为向量必须将所有后续元素复制回一个插槽以填充空洞) 。哪种数据结构最适合您的用途取决于您使用数据做了什么。

答案 1 :(得分:4)

首先,std :: vector erase方法不是很有效,它必须在删除之后移动所有项目。如果矢量项(子弹)的顺序无关紧要,将删除的项目符号与最后一个项目符号交换并删除最后一个项目符号将会更快(因此您将获得持续的复杂性而非线性复杂性)。

第二,真正的问题是什么 - 删除10,000项后,内存没有被释放?我们是在讨论操作系统报告的可用内存还是堆上的可用空间?有可能(并且非常可能)在向量数据的位置之后分配了一些其他对象,因此不可能简单地将该存储器释放到操作系统;但它可以重复用于其他新创建的对象。

答案 2 :(得分:3)

这就是向量的内存分配模型通常用于提供一个分摊的常量时间push_back操作的方式,基本上它试图猜测你可能想要用一个新元素填充已擦除的部分,因此它不会释放记忆。通过这样做,它可以避免不断的分配和解除分配。要解决此问题,您可以使用交换技巧释放未使用的向量内存。你必须将空向量与一个临时的未命名向量交换,这样当临时向量超出范围时,它会释放析构函数中的内存,例如:vector<int>(c).swap(c)

答案 3 :(得分:3)

我建议你看看这两个成语,然后选择最适合你的成语:
Shrink to fit
Clear & minimize

答案 4 :(得分:2)

它可能无法摆脱记忆 但是下次你需要添加一个bullit时,它不需要重新分配更多的空间 它不会重复使用已擦除子弹的内存。

注意:
如果您经常从容器中间擦除,那么矢量可能不是正确的容器。这是因为如果删除元素n,那么[n + 1,end)中的所有元素必须向下移动到内存中的一个空格。

答案 5 :(得分:0)

  

当子弹完成后,我会做bullets.erase(bullets.begin()+ i);

不要那样做。如果每帧完成多个子弹,你会获得可怕的性能,因为未完成的子弹将被反复复制,这实际上是不必要的。这就是我要做的事情:

#include <algorithm>
#include <functional>
#include <vector>

class Bullet
{
    // ...
public:
    bool is_finished() const;
};

int main()
{
    std::vector<Bullet> bullets;
    // ...
    bullets.erase(
        std::remove_if(
            bullets.begin(),
            bullets.end(),
            std::mem_fun_ref(&Bullet::is_finished)
        ),
        bullets.end()
    );
}

这种方法最多只移动一次活弹。