I'm programming a Breakout game in C++. I'm having a HUGE problem that's preventing me from giving the game multi-ball functionality. I think it has something to do with the destructor. Have a look:
for loop for the balls (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
(...)
}
And I get this error:
Debug Error!
Program: Breakout.exe
abort() has been called
(Press Retry to debug the application)
Do you know why this mysterious crash is happening? Or more importantly, a fix for it?
Here's a replicable piece of code to help:
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;
}
You cannot modify a container while you are iterating through it with a
range-forloop. You don't have access to theiteratorthat the loop uses internally, anderase()will invalidate thatiterator.You can use the container's
iterators manually, paying attention to the newiteratorthaterase()returns, eg:Alternatively, use the erase-remove idiom via
std::remove_if()instead:UPDATE: now that you have posted more of your code, it is clear to see that you are trying to use ID numbers as indexes into the vector, but you are not implementing those IDs correctly, and they are completely unnecessary and should be eliminated.
The
Ball::ballIDmember is never being assigned any value, so in this statement:Trying to
erase()the result ofballs.begin() + b.getID()causes undefined behavior since the iterator has an indeterminate value, thus you can end up trying to erase the wrongBallobject, or even an invalidBallobject (which is likely the root cause of your runtime crash).Also, in this section of code:
Since you want to access the
Ballobject you just pushed, that code can be simplified to this:And I already gave you code further above to show you how to remove balls from the vector without using IDs.
So, there is need for an ID system at all.
With that said, try something more like this:
Driver.cpp:Ball.h:Ball.cpp: