【问题标题】:Memory leaks in a pointer vector指针向量中的内存泄漏
【发布时间】:2019-04-18 15:28:19
【问题描述】:

我正在制作一个使用大量包含指针的向量的程序,我担心我删除指针的方式可能会导致内存泄漏。

例如,我如何将项目符号添加到矢量:

Bullet* aNewBullet;
aNewBullet = new Bullet(x,y,dirX,dirY,size,duration);
aNewBullet->assignTexture(btext);
bulletVector.push_back(aNewBullet);

这是我从向量中删除项目符号的方法(例如,如果它超出了它的范围)

int i=0;
while (i<bulletVector.size()){
    if (bulletVector[i]->getDuration() ==0)
        bulletVector.erase(bulletVector.begin() + i);
    else i++;
}

我是否通过告诉矢量擦除来删除项目符号? 或者我应该这样做

int i=0;
while (i<bulletVector.size()){
    if (bulletVector[i]->getDuration() ==0)
        delete bulletVector[i];
        bulletVector.erase(bulletVector.begin() + i);
    else i++;
}

相反?这会不会先删除指针,然后再次从向量中删除记录,从而弄乱向量大小?

删除指针也会调用子弹析构函数(~Bullet())吗?

【问题讨论】:

  • 你需要使用new/delete吗?为什么不直接使用std::vector&lt;Bullet&gt;
  • 原始指针的集合并不是一个好主意。集合要么拥有指向的对象,要么不拥有。如果它确实拥有它们,为什么要使用指针呢?如果它不拥有它们,那么管理对象的生命周期就会变得非常复杂。

标签: c++ pointers vector memory-leaks


【解决方案1】:

首先,很有可能您的问题可以通过不首先使用手动动态分配来解决。您可以使用动态分配(例如多态存储)是有原因的,但即便如此,您也应该转向智能指针而不是手动分配。

放弃指针

要使用具体对象(不是指针)的常规向量来做到这一点,您只需

std::vector<Bullet> bulletVector;

可以通过以下方式添加:

bulletVector.emplace_back(x,y,dirX,dirY,size,duration);
bulletVector.back().assignTexture(btext);

根据持续时间达到零来删除所有元素只需使用remove/erase 成语,如下所示:

bulletVector.erase(std::remove_if(
                    bulletVector.begin(), 
                    bulletVector.end(),
                    [](auto const& b) { return b.getDuration() == 0; }),
                   bulletVector.end());

请记住,虽然这很容易成为存储这些内容的首选方法,但它需要更改您的其余代码。您目前在任何地方都这样做:

bulletVector[i]->doSomething();

会变成

bulletVector[i].doSomething();

更智能的指针

如果必须使用动态分配,不要使用手动管理的指针。它们是内存泄漏和所有权责任的秘诀,如果你认为它不会发生在你身上,那你就太天真了。相反,使用智能指针。两个主要的智能指针模板是std::unique_ptrstd::shared_ptr。还有std::weak_ptr,值得一提,因为它在某些情况下非常方便(我知道不是你的情况)。

std::vector<std::shared_ptr<Bullet>> bulletVector;

因此可以像这样进行添加(假设这是考虑到多态性并且可能有不止一种子弹类型,在这种情况下MagicBulletSilverBullet,两者都派生自Bullet):

std::shared_ptr<Bullet> bullet = std::make_shared<MagicBullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

bullet = std::make_shared<SilverBullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

或者只存储普通子弹:

bullet = std::make_shared<Bullet>(x,y,dirX,dirY,size,duration);
bullet->assignTexture(btext);
bulletVector.emplace_back(bullet);

去除算法是一样的,只是条件谓词函数变化(变化不大)

bulletVector.erase(std::remove_if(
                    bulletVector.begin(), 
                    bulletVector.end(),
                    [](auto const& b) { return b->getDuration() == 0; }),
                   bulletVector.end());

其中最好的部分是您的代码的其余部分可能几乎不需要任何更改。但我强调,这是唯一真正的好处(除了显而易见的,即不必再自己管理内存)。


鉴于两种方法(具体对象的向量与智能指针的向量)之间的选择,请谨慎选择。如果前者有效,请使用它。如果没有,请考虑它如何工作,或者使用后者。

但最重要的是,停止手动管理内存

【讨论】:

  • 就我个人而言,我想推荐std::unique_ptr 而不是std::shared_ptr,因为std::shared_ptrs 有自己的一系列问题,只有在实际共享所有权 是必需(两个或多个对象需要引用同一个对象,无法知道哪个会最后死)。
  • @Galik 当然,适当的用例应该使用适当的指针类型。 OP 的代码中没有证据表明实例是唯一的,并且在这两种情况下都可以共享。如果它们确实是唯一的,那么使用智能指针(与具体对象相反)的唯一原因是出于多态原因(同样,需要零证据,但这是一个重要的未知数)。无论如何,概念是一样的。
【解决方案2】:

当然,你必须自己通过delete来删除Bullets。但是你的删除周期不正确。

要在某些条件下从向量中删除值,请使用以下内容:

auto condFunc = [](const Bullet* x)
{
    bool rez = (x->getDuration() == 0);
    if(rez)
        delete x;
    return rez;
};
bulletVector.erase(std::remove_if(bulletVector.begin(), bulletVector.end(), condFunc), bulletVector.end());

【讨论】:

  • 您的示例代码也不会删除指针,尽管这是从向量中删除它们的更好方法。
  • @PeteKirkham 这个例子是“从向量中删除值”,而不是释放内存
  • 在这种情况下,它不能回答问题,即从向量中删除它们并从堆中删除它们。在 C++ 中,没有“从向量中删除值”之类的东西,因为“删除”具有非常特定的含义。
【解决方案3】:

您需要delete 使用new 创建的对象(除非您放弃指向std::unique_ptrstd::shared_ptr 的指针)。考虑让向量成为对象的所有者。

std::vector<Bullet> bulletVector;

// in C++17, emplace_back returns a ref to the new Bullet:
Bullet& aNewBullet = bulletVector.emplace_back(x,y,dirX,dirY,size,duration);

// pre C++17:
bulletVector.emplace_back(x,y,dirX,dirY,size,duration);
Bullet& aNewBullet = bulletVector.back();

aNewBullet.assignTexture(btext);

当你现在erase()一个来自vector的对象时,它会被自动销毁。

【讨论】:

  • 向量被声明为vector bulletVector;在课堂上,删除星号是否正确?
  • 不,我建议 OP 应该考虑将其声明为 std::vector&lt;Bullet&gt; bulletVector; - 如果它当然不会弄乱其余的设计。
【解决方案4】:

在第一个代码示例中,您没有删除任何 Bullet 对象。擦除vector 只会删除指针,不会删除指向的对象。

您的第二个示例确实正确删除了Bullets(并非所有这些都给出了条件和循环设置),但前提是您在修改向量的所有点都进行了必要的删除并手动确保指针永远不会删除了两次(例如,因为它存在于两个向量中)。这不是容器应该被使用的方式。

如果您真的想存储指针并让向量拥有它们,那么您应该使用std::vector&lt;std::unique_ptr&lt;Bullet&gt;&gt;,它会在向量被擦除时正确删除Bullet 对象。然而,向量将变得不可复制(只能移动)。

如果无论如何该向量应该拥有Bullets,那么您可以简单地将它们直接存储在向量中:std::vector&lt;Bullet&gt;

如果向量不应该拥有Bullets,那么删除它们的责任在于实际拥有它们的任何对象,但即便如此,也需要做出一些努力来确保已删除的指针不会保留在向量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 2012-09-13
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    相关资源
    最近更新 更多