【问题标题】:How do vectors clean up after themselves? (Or do they?)载体如何自我清理? (还是他们?)
【发布时间】:2024-05-01 18:50:01
【问题描述】:

我正在为我的编程课制作一个模拟飞碟射击的游戏。 主要的Game 类包含std::vector<Bullet> bullets,其中Bullet 是我的类。它仅包含具体类型的成员,并覆盖基类Drawable 来绘制和更新自身。它没有重新定义任何运算符。在每一帧,bullets 都会循环播放,每个子弹都会更新和绘制。

当用户按下一个键时,我将Bullet 添加到bullets,如下所示:

在游戏中::handleInput:

if (ui.isSpaceBar())
{
   Bullet newBullet = rifle.fire();
   addBullet(newBullet);
}

rifle.fire 像这样返回Bullet

Bullet Rifle :: fire()
{
   ... calculation of position and velocity...
   return Bullet(...position and velocity values...);
}

addBullet 看起来像这样:

void addBullet(const Bullet & newBullet)
{
   bool assigned = false;
   for (int i = 0; i < bullets.size(); i++)
   {
      // If the i'th bullet is off-screen or hit something ...
      if (bullets[i].isDead())
      {
         // ... then overwrite it with the new bullet,
         // instead of using push_back, to avoid constantly
         // increasing the size of the vector
         bullets[i] = newBullet;
         assigned = true;
         break;
      }
   }

   if (!assigned)
      bullets.push_back(newBullet);
}

我的问题是关于bullets[i] = newBullet; 这一行。在使用索引器覆盖向量中的值之前,我是否需要 delete 任何东西?如果我理解正确,使用值而不是指针(Bullet 代替 Bullet *return Bullet(...) 代替 return new Bullet(...))意味着我不必调用 deleteBullet 实例是当它们超出范围时自动销毁,这将是当向量超出范围时。那正确吗?如果是这样,是否使用向量的索引器运算符delete 旧值(在这种情况下为“死”项目符号)?如果没有,我错过了什么?我应该使用指针而不是值吗?

【问题讨论】:

  • 不,你不需要调用delete。事实上,这样做可能会使您的程序崩溃。您不能删除未明确新建的内容。如果向量的所有元素都是同一类型,则不需要使用指针。

标签: c++ pointers memory-management vector


【解决方案1】:

当您使用具体对象(例如 Bullet)而不是指向对象的指针时,您不需要使用 delete。

所以bullets[i] = myBullet 复制了myBullet。它将bullets[i] 的旧值分配给myBullet 的新值。当向量超出范围时,它将调用它的每个元素的析构函数,该析构函数会清除该元素持有的所有内存。

为了技术上正确,上述语句使用赋值运算符 (operator=) 来进行复制。这样就可以清理旧的bullet 并将新的bullet's 成员变量复制到旧的bullet's 成员变量中。

bullets.push_back(myBullet) 类似。除了它将myBullet 添加到向量的末尾。所以这意味着如果向量的大小为 9,它现在将是 10。

从技术上讲是正确的,push_back 使用复制构造函数,而不是赋值运算符来构造新对象(我认为),因为没有现有的旧 bullet 对象。

底线是给定一个正确实现的具体类(无论它在内部多么复杂),您可以在std::vector 中使用它们的实例,而不必担心进行任何内存清理。

【讨论】:

  • 谢谢。这证实了我以为我知道的。不过,我仍然不清楚的是,当bullets[i] 已经有一个值时,myBullet 被分配给它时会发生什么。现有值(“死”项目符号)是否已销毁,并自动清除所有分配?
  • 是的。但是我在答案中添加了更完整的解释。通过查看上述 B​​ullet 的成员函数,您可以更好地了解这些操作的工作原理。
  • @MichaelHoffmann 使用了 赋值运算符。没有构造和破坏(除非赋值运算符函数通过破坏旧的并构造新的来显式地实现自身)。如果您没有声明自己的 Bullet::operator= 函数,编译器会为您创建一个,其行为就像您使用 x = other.x; 等一样,其中 x 依次是类的每个成员变量。
  • 啊,我明白了。因此,就处理 other 而言,默认实现什么也不做?默认赋值运算符如何处理指针? (更好的是,我可以在哪里阅读更多相关信息?)
  • @MichaelHoffmann:您需要搜索Rule of Three——三个成员函数几乎总是一起出现:复制构造函数、赋值运算符和析构函数。或者Rule of Zero - 如果他们不需要这三个中的任何一个,课程会更容易。