push_back(*obj) 正在调用析构函数并导致 SDL_BlitSurface 崩溃
push_back(*obj) is calling destructor and is causing SDL_BlitSurface to crash
我是 C++ 中 SDL 编程的新手,所以我正在开发一个简单的 Invaders 游戏。
里面我有几个classes,其中之一当然是外星人class。 Alien 的析构函数调用 SDL_FreeSurface(SPRITEOFALIEN),以删除对象。
我在游戏中也有一个矢量 class,如果只有一个外星人,一切都很好,但是我一生成另一个外星人,我就调用 push_back 将其添加到向量中,析构函数被调用,因此当涉及到实际绘制外星人的向量时,SDL_BlitSurface 使程序崩溃。
示例代码:
vector<Alien> aliens;
// More code.... and eventually
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
alien = new Alien(150, 0); // Alien* alien
aliens.push_back(*alien);
}
// Some more code... advancing the aliens, etc...
std::vector<Alien>::iterator it2;
for (it2 = aliens.begin(); it2 != aliens.end(); ++it2)
SDL_BlitSurface(it2->getSprite(), 0, screen, it2->getRect()); /// CRASH, FIXME
我查了其他类似的问题,但他们都建议有一个固定大小的向量,我认为这对我的情况来说不是一个好的解决方案,因为应该有任意数量的外星人(还有导弹) ,我对他们也有同样的问题。
我也知道它发生是因为当调用 push_back 时,向量被复制到一个新的更大的向量,这就是调用析构函数的原因。然而由于某种原因,drawObjects 函数捕获了 OLD 向量并崩溃了...
有办法解决这个问题吗?
外星人Class定义:
#include "alien.h"
Alien::Alien(int x, int y)
{
rect.x = x; // x-pos
rect.y = y; // y-pos
sprite = SDL_LoadBMP("alien.bmp");
}
Alien::~Alien()
{
SDL_FreeSurface(sprite)
printf("Deleted Alien\n");
}
void Alien::move()
{
rect.y += SPEED;
}
很可能 Alien
class 使用 new 分配一些指针,在销毁时释放它们并且没有复制构造函数。在那种情况下,默认实现是复制指针,而不是分配对象的新副本。您应该为 Alien
编写一个复制构造函数来精确地执行此操作。如果你想象 member
是 Alien
(class Member
)的唯一成员,那看起来像这样:
Alien(const Alien & a) {
member = new Member(*member);
}
当然,如果反过来Member
分配一些对象,你也应该在那里创建正确的复制构造函数。
首先,您在复制对象时遇到的问题很可能是您破坏了 "rule of three" -http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)
如果你的对象不能被复制,你应该禁止复制 and/or 移动(例如使 copy/move ctor 私有)至少编译器会告诉你是否有问题并且你尝试复制他们。
现在,为了避免在向量中复制对象,您需要在向量中保留足够的 space 并在创建时调用 emplace
(如果您可以使用 C++11)。或者你应该通过指针来保存对象。您动态创建实例的代码建议您或其他人计划这样做,但出于任何原因将其按值放入 vector<Alien>
中。此外,您不应该通过 vector
中的原始指针保留对象(除非您有足够的经验并且知道自己在做什么)所以您应该使用智能指针,如果您使用 C++11 或来自 boost 或类似库的标准否则:
typedef std::shared_ptr<Alien> AlienPtr;
typedef std::vector<AlientPtr> Aliens;
Aliens aliens;
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
aliens.push_back( std::make_shared<Alien>( 150, 0 ) );
}
向量大小不同的根本问题是,当大小增加时,向量可能必须重新分配并将其所有内容放在新位置。这将复制所有现有内容,然后销毁旧对象。
由于您不希望您的对象被破坏(清理 SDL 表面),我们需要安排向量复制并破坏其他东西。它可能是原始指针,但更好的方法是使用智能指针,例如 std::unique_ptr
.
试试这个:
std::vector<std::unique_ptr<Alien>> aliens;
...
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
alien = new Alien(150, 0); // Alien* alien
aliens.emplace_back(alien);
}
然后当你使用它时,你需要取消引用它...
for (std::unique_ptr<Alien>& that_alien : aliens)
SDL_BlitSurface(that_alien->getSprite(), 0, screen, that_alien->getRect());
这避免了使用损坏的 Alien 复制构造函数。您应该更进一步并禁用它以确保它不会被意外调用。在您的 class 定义中,添加:
class Alien
{
// add one of the two lines below
private: Alien(const Alien&) = delete; // if your compiler has = delete implemented
private: Alien(const Alien&); // this is almost as good
};
您的问题可能来自 Alien
的副本。当副本被破坏时,它将破坏所有副本共享的表面资源。如果在您调用 push_back()
时制作了多个副本——我敢打赌至少有两个副本——那么仅在这一行上,您就双重释放了 SDL_Surface *
,这很可能会导致崩溃.
我强烈建议定义一个类型专门 以 RAII 方式管理表面,然后使用 Alien
中的类型,因为您将获得正确的语义默认。
例如:
struct SDLSurfaceDeleter
{
void operator()(SDL_Surface * p) const
{
if (p) { SDL_FreeSurface(p); }
}
};
typedef std::unique_ptr<SDL_Surface, SDLSurfaceDeleter> UniqueSDLSurface;
现在您的 Alien
class 您有:
private:
UniqueSDLSurface sprite;
在你的构造函数中:
sprite = UniqueSDLSurface(SDL_LoadBMP("alien.bmp"));
(或者,更好的是,在初始化列表中初始化它:sprite(SDL_LoadBMP("alien.bmp"))
。使用初始化列表方法,您可以在第一时间正确构造 UniqueSDLSurface
对象;使用赋值方法,您默认-构造它然后移动分配一个新对象。两者都可以,但初始化列表方法更干净。)
最后,删除 Alien
析构函数。现在,Alien
class 应该可以自动移动,但不能复制。如果您在复制 Alien
对象时遇到任何编译时错误,您需要修复这些错误;它们首先是您问题的根源,使用这段代码,编译器 不再让复制发生 ,这是一件好事!
在适当的 C++ 标准库中,std::vector
将在重新分配期间尽可能移动对象,因此现在您将能够正确使用它,因为它会将您的 Alien
对象移动到他们的新目的地,而不是复制他们。
作为旁注,您可以类似地使用 std::shared_ptr
创建一个共享表面,当最后一个 std::shared_ptr
被销毁时,该表面将被释放。这在这里可能更有意义,因为所有 Alien
对象都使用相同的源位图;您可以加载一次,然后在所有实例之间共享它:
std::shared_ptr<SDL_Surface> make_shared_surface(SDL_Surface * surface)
{
return std::shared_ptr<SDL_Surface>(surface, SDLSurfaceDeleter());
}
此外,这里你泄漏了内存,因为你没有 delete
你用 new
:
分配的对象
alien = new Alien(150, 0); // Alien* alien
aliens.push_back(*alien);
但是你不需要这样做,因为你可以这样做:
aliens.push_back(Alien(150, 0));
或者,更好的是:
aliens.emplace_back(150, 0);
我是 C++ 中 SDL 编程的新手,所以我正在开发一个简单的 Invaders 游戏。
里面我有几个classes,其中之一当然是外星人class。 Alien 的析构函数调用 SDL_FreeSurface(SPRITEOFALIEN),以删除对象。
我在游戏中也有一个矢量 class,如果只有一个外星人,一切都很好,但是我一生成另一个外星人,我就调用 push_back 将其添加到向量中,析构函数被调用,因此当涉及到实际绘制外星人的向量时,SDL_BlitSurface 使程序崩溃。
示例代码:
vector<Alien> aliens;
// More code.... and eventually
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
alien = new Alien(150, 0); // Alien* alien
aliens.push_back(*alien);
}
// Some more code... advancing the aliens, etc...
std::vector<Alien>::iterator it2;
for (it2 = aliens.begin(); it2 != aliens.end(); ++it2)
SDL_BlitSurface(it2->getSprite(), 0, screen, it2->getRect()); /// CRASH, FIXME
我查了其他类似的问题,但他们都建议有一个固定大小的向量,我认为这对我的情况来说不是一个好的解决方案,因为应该有任意数量的外星人(还有导弹) ,我对他们也有同样的问题。
我也知道它发生是因为当调用 push_back 时,向量被复制到一个新的更大的向量,这就是调用析构函数的原因。然而由于某种原因,drawObjects 函数捕获了 OLD 向量并崩溃了...
有办法解决这个问题吗?
外星人Class定义:
#include "alien.h"
Alien::Alien(int x, int y)
{
rect.x = x; // x-pos
rect.y = y; // y-pos
sprite = SDL_LoadBMP("alien.bmp");
}
Alien::~Alien()
{
SDL_FreeSurface(sprite)
printf("Deleted Alien\n");
}
void Alien::move()
{
rect.y += SPEED;
}
很可能 Alien
class 使用 new 分配一些指针,在销毁时释放它们并且没有复制构造函数。在那种情况下,默认实现是复制指针,而不是分配对象的新副本。您应该为 Alien
编写一个复制构造函数来精确地执行此操作。如果你想象 member
是 Alien
(class Member
)的唯一成员,那看起来像这样:
Alien(const Alien & a) {
member = new Member(*member);
}
当然,如果反过来Member
分配一些对象,你也应该在那里创建正确的复制构造函数。
首先,您在复制对象时遇到的问题很可能是您破坏了 "rule of three" -http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)
如果你的对象不能被复制,你应该禁止复制 and/or 移动(例如使 copy/move ctor 私有)至少编译器会告诉你是否有问题并且你尝试复制他们。
现在,为了避免在向量中复制对象,您需要在向量中保留足够的 space 并在创建时调用 emplace
(如果您可以使用 C++11)。或者你应该通过指针来保存对象。您动态创建实例的代码建议您或其他人计划这样做,但出于任何原因将其按值放入 vector<Alien>
中。此外,您不应该通过 vector
中的原始指针保留对象(除非您有足够的经验并且知道自己在做什么)所以您应该使用智能指针,如果您使用 C++11 或来自 boost 或类似库的标准否则:
typedef std::shared_ptr<Alien> AlienPtr;
typedef std::vector<AlientPtr> Aliens;
Aliens aliens;
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
aliens.push_back( std::make_shared<Alien>( 150, 0 ) );
}
向量大小不同的根本问题是,当大小增加时,向量可能必须重新分配并将其所有内容放在新位置。这将复制所有现有内容,然后销毁旧对象。
由于您不希望您的对象被破坏(清理 SDL 表面),我们需要安排向量复制并破坏其他东西。它可能是原始指针,但更好的方法是使用智能指针,例如 std::unique_ptr
.
试试这个:
std::vector<std::unique_ptr<Alien>> aliens;
...
if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
alien = new Alien(150, 0); // Alien* alien
aliens.emplace_back(alien);
}
然后当你使用它时,你需要取消引用它...
for (std::unique_ptr<Alien>& that_alien : aliens)
SDL_BlitSurface(that_alien->getSprite(), 0, screen, that_alien->getRect());
这避免了使用损坏的 Alien 复制构造函数。您应该更进一步并禁用它以确保它不会被意外调用。在您的 class 定义中,添加:
class Alien
{
// add one of the two lines below
private: Alien(const Alien&) = delete; // if your compiler has = delete implemented
private: Alien(const Alien&); // this is almost as good
};
您的问题可能来自 Alien
的副本。当副本被破坏时,它将破坏所有副本共享的表面资源。如果在您调用 push_back()
时制作了多个副本——我敢打赌至少有两个副本——那么仅在这一行上,您就双重释放了 SDL_Surface *
,这很可能会导致崩溃.
我强烈建议定义一个类型专门 以 RAII 方式管理表面,然后使用 Alien
中的类型,因为您将获得正确的语义默认。
例如:
struct SDLSurfaceDeleter
{
void operator()(SDL_Surface * p) const
{
if (p) { SDL_FreeSurface(p); }
}
};
typedef std::unique_ptr<SDL_Surface, SDLSurfaceDeleter> UniqueSDLSurface;
现在您的 Alien
class 您有:
private:
UniqueSDLSurface sprite;
在你的构造函数中:
sprite = UniqueSDLSurface(SDL_LoadBMP("alien.bmp"));
(或者,更好的是,在初始化列表中初始化它:sprite(SDL_LoadBMP("alien.bmp"))
。使用初始化列表方法,您可以在第一时间正确构造 UniqueSDLSurface
对象;使用赋值方法,您默认-构造它然后移动分配一个新对象。两者都可以,但初始化列表方法更干净。)
最后,删除 Alien
析构函数。现在,Alien
class 应该可以自动移动,但不能复制。如果您在复制 Alien
对象时遇到任何编译时错误,您需要修复这些错误;它们首先是您问题的根源,使用这段代码,编译器 不再让复制发生 ,这是一件好事!
在适当的 C++ 标准库中,std::vector
将在重新分配期间尽可能移动对象,因此现在您将能够正确使用它,因为它会将您的 Alien
对象移动到他们的新目的地,而不是复制他们。
作为旁注,您可以类似地使用 std::shared_ptr
创建一个共享表面,当最后一个 std::shared_ptr
被销毁时,该表面将被释放。这在这里可能更有意义,因为所有 Alien
对象都使用相同的源位图;您可以加载一次,然后在所有实例之间共享它:
std::shared_ptr<SDL_Surface> make_shared_surface(SDL_Surface * surface)
{
return std::shared_ptr<SDL_Surface>(surface, SDLSurfaceDeleter());
}
此外,这里你泄漏了内存,因为你没有 delete
你用 new
:
alien = new Alien(150, 0); // Alien* alien
aliens.push_back(*alien);
但是你不需要这样做,因为你可以这样做:
aliens.push_back(Alien(150, 0));
或者,更好的是:
aliens.emplace_back(150, 0);