如何在向量中的对象上获取析构函数而不引发失败的断言?

How do I get a destructor on an object in a vector not to throw a failed assertion?

我正在用 C++ 编写 Breakout 游戏。我遇到了一个巨大的问题,阻止我为游戏提供多球功能。我认为这与析构函数有关。看看:

for 球循环 (Driver.cpp):

for (Ball& b : balls) { // Loops over all balls
    (...)
    // Collision for when you miss
    if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
    balls.erase(balls.begin() + b.getID()); // Wipe the ball out of memory to make room (Troublesome line)
    Ball::addToID(-1); // Shift the ball ID to assign to the next ball back one
    (...)
}

我得到这个错误:

Debug Error!
Program: Breakout.exe
abort() has been called
(Press Retry to debug the application)

你知道为什么会发生这种神秘的崩溃吗?或者更重要的是,修复它?

这里有一段可复制的代码可以提供帮助:

Driver.cpp:

#include <vector>
#include <allegro5\allegro.h>
#include "Ball.h"

using namespace std;

vector<Ball> balls(0); // Pay attention to this line

const POS WIDTH = 1600, HEIGHT = 900;


int main {
    while (running) {
        if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
                balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
                balls[Ball::getIDtoAssign()].setYSpeed(5);
            }

            for (Ball& b : balls) { // Pay attention to this loop
                b.draw(); // This line is what's crashing.
                b.move();

                (...)
                // Collision for when you miss
                balls.erase(
                    remove_if(balls.begin(), balls.end(),
                        [=](Ball& b) {
                            // Collision for when you miss
                            return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
                        }
                    ),
                    balls.end()
                            );
                }
            }
        }
    }
    return 0;
}

Ball.h:

#pragma once
#include <allegro5\allegro_primitives.h>

using namespace std;

class Ball {
public:
    Ball();
    Ball(float x, float y, float w, float h);
    ~Ball();
    void draw();
    void move();
    float getYPos();
    void setYSpeed(float set);
private:
    float xPos; // Horizontal position
    float yPos; // Vertical position (upside down)
    float width; // Sprite width
    float height; // Sprite height
    float xSpeed; // Horizontal speed
    float ySpeed; // Vertical speed (inverted)
}

Ball.cpp:

#include "Ball.h"

short Ball::ballIDtoAssign = 0;

Ball::Ball() {
    this->xPos = 0;
    this->yPos = 0;
    this->width = 0;
    this->height = 0;
    this->xSpeed = 0;
    this->ySpeed = 0;
}

Ball::Ball(float x, float y, float w, float h) {
    this->xPos = x;
    this->yPos = y;
    this->width = w;
    this->height = h;
    this->xSpeed = 0;
    this->ySpeed = 0;
}

Ball::~Ball() {
    // Destructor
}

void Ball::draw() {
    al_draw_filled_rectangle(xPos, yPos, xPos + width, yPos + height, al_map_rgb(0xFF, 0xFF, 0xFF));
}

void Ball::move() {
    xPos += xSpeed;
    yPos += ySpeed;
}

float Ball::getYPos() {
    return yPos;
}

void Ball::setYSpeed(float set) {
    ySpeed = set;
}

在使用 range-for 循环遍历容器时不能修改容器。您无权访问循环内部使用的 iteratorerase() 将使 iterator.

无效

可以手动使用容器的iterator,注意erase() returns新增的iterator,eg:

for(auto iter = balls.begin(); iter != balls.end(); ) { // Loops over all balls
    Ball& b = *iter: 
    ...
    // Collision for when you miss
    if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
        ...
        iter = balls.erase(iter); // Wipe the ball out of memory to make room
    }
    else {
        ++iter;
    }
}

或者,通过 std::remove_if() 使用 erase-remove idiom

balls.erase(
    std::remove_if(balls.begin(), balls.end(),
        [=](Ball &b){
            // Collision for when you miss
            return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
        }
    ),
    balls.end()
);

更新:现在您已经发布了更多代码,可以清楚地看到您正在尝试使用 ID 号作为向量的索引,但您并没有这样做正确实施这些 ID,它们完全没有必要,应该被淘汰。

Ball::ballID 成员从未被赋予任何值,因此在此语句中:

balls.erase(balls.begin() + b.getID()); // The troublesome line

尝试 erase() balls.begin() + b.getID() 的结果导致 未定义的行为 因为迭代器有一个 不确定的 值,因此您最终可能会尝试擦除错误的 Ball 对象,甚至是 invalid Ball 对象(这可能是运行时崩溃的根本原因) .

另外,在这段代码中:

balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls[Ball::getIDtoAssign()].setYSpeed(5);
Ball::addToID(1);

因为你想访问你刚刚推送的 Ball 对象,该代码可以简化为:

balls.back().setYSpeed(5);

我已经在上面进一步提供了代码,向您展示了如何在不使用 ID 的情况下从矢量中删除球。

所以,根本就需要一个 ID 系统。

话虽如此,试试这样的东西:

Driver.cpp:

#include <vector>
...
#include "Ball.h"
using namespace std;

vector<Ball> balls;
const POS WIDTH = 1600, HEIGHT = 900;

int main {
    ...

    while (running) {
        ...
        
        if (input.type == ALLEGRO_EVENT_TIMER) { // Runs at 60FPS
            ...

            if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
                balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
                balls.back().setYSpeed(5);
            }

            for (auto iter = balls.begin(); iter != balls.end(); ) {
                Ball &b = *iter;
                ...

                if (b.getYPos() > HEIGHT) { // Collision for when you miss
                    iter = balls.erase(iter);
                }
                else {
                    ++iter;
                }
            }

            /* alternatively:

            for (Ball& b : balls) {
                b.draw();
                b.move();
            }

            balls.erase(
                std::remove_if(balls.begin(), balls.end(),
                    [=](Ball &b){
                        // Collision for when you miss
                        return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
                    }
                ),
                balls.end()
            );
            */
        }
    }
    return 0;
}

Ball.h:

#pragma once
...

class Ball {
public:
    ...
    // NO ID METHODS!

private:
    ...
    // NO ID MEMBERS!
}

Ball.cpp:

#include "Ball.h"

...

// NO ID MEMBER/METHODS!

好的,所以我设法弄清楚了程序崩溃的原因。这是因为我在 for 循环中使用了 erase-remove 这会导致各种问题。