如何在向量中的对象上获取析构函数而不引发失败的断言?
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
循环遍历容器时不能修改容器。您无权访问循环内部使用的 iterator
,erase()
将使 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 这会导致各种问题。
我正在用 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
循环遍历容器时不能修改容器。您无权访问循环内部使用的 iterator
,erase()
将使 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 这会导致各种问题。