所以问题是当我尝试将非动态obj推送到playerList或当我尝试删除n时,我得到段错误(核心转储)。我假设问题是在 Helper 类被销毁时引起的,因此向量也被销毁,因此它试图销毁不再存在的对象本身。但是,当我使用playerList.clear()时,问题仍然存在。我想我可以用 ~Helper() 销毁 playerList() 中的对象。但是我想知道为什么我不能使用非动态对象,而是在 Run() 结束时将它们从 playerList 中清除。
class Helper{
public:
void Run();
private:
std::vector<Player>playerList;
...
};
这就是 Run() 的样子:
using namespace std;
void Helper::Run(){
Player *n = new Player();
playerList.push_back(*n); //Yup. There is a memleak
}
也 玩家.h:
class Player{
public:
...
~Player();
private:
...
IClass* typeOfClass = new Warrior();
};
和 ~播放器:
Player::~Player(){
delete typeOfClass;
}
和勇士(对问题没有影响)
class Warrior {
public:
int GetMeleeAttack();
int GetRangedAttack();
int GetMagicAttack();
int AgilityAction();
int StrengthAction();
int IntelligenceAction();
void WhoAmI();
private:
};
Warrior 的方法只返回一些整数。
std::vector<Player>playerList;
应该是
std::vector<Player*>playerList;
如果要动态分配它们。另一种方法是放置每个元素而不是使用 new。
使用 new 时,您是在堆上分配,但您是通过传递堆上分配的值在向量中创建新元素。并且您有一个悬空的指针(内存泄漏)
如果您使用的是指针向量,请记住在销毁向量时释放所有元素。
另一种方法是:
std::vector<std::unique_ptr<Player> >playerList;
这将解决分配问题。
std::vector
的主要工作是为您动态分配对象,因此您无需使用new
和delete
。
void Helper::Run(){
playerList.push_back(Player());
}
这将默认构造一个新的播放器对象并将其添加到向量中。
解决方案
是的,这个问题可以通过存储指向Player
的指针来解决,但这不必要地增加了必须在Player
内存储指向IClass
的指针引起的问题。
相反,你可以通过使整个问题消失
IClass* typeOfClass;
到
std::unique_ptr<IClass> typeOfClass;
或
std::shared_ptr<IClass> typeOfClass;
并使用
playerList.emplace_back();
在Helper::Run
.
TL;博士
std::vector
做了很多复制和销毁副本的工作。为此,vector
包含的对象必须符合三、五或零规则之一。
由于Player
拥有一个指针并使用默认复制函数,因此不遵守 3/5/0 要求的规则。因此,副本的typeOfClass
指向与源typeOfClass
相同的位置。销毁源或副本时,它会删除源和副本都正在使用的typeOfClass
,而另一个指向无效内存。该程序现已损坏,可能会也可能不会崩溃。
但是,如果Player
遵守规则并具有如下所示的移动构造函数和移动赋值运算符
class Player{
public:
...
~Player();
Player(Player && src)
{
typeOfClass = src.typeOfClass;
src.typeOfClass = nullptr;
}
Player& operator=(Player && src)
{
typeOfClass = src.typeOfClass;
src.typeOfClass = nullptr;
}
private:
...
IClass* typeOfClass = new Warrior();
};
然后vector
可以四处移动Players
,而无需两个Player
共享相同的数据,Helper::Run
看起来像
void Helper::Run(){
playerList.emplace_back();
}
不会有内存泄漏,并且游戏中的指针更少。
然而。。。您真正想要的是尽可能少地进行特殊处理。每个特殊情况都意味着更多的测试。如果你能从源头上保护typeOfClass
,那么Player
就可以作为一个职位愚蠢,并遵守零法则。
在这种情况下,这意味着使用代理对象(如智能指针)来获取typeOfClass
的所有权并为您管理其生命周期。如果所有勇士都可以拥有相同的Warrior
实例,那就意味着std:shared_ptr
。如果勇士需要单独的Warrior
实例来处理包含Player
实例的簿记,则需要unique_ptr
。