【问题标题】:C++ trouble with deallocating memory taken by vector elementsC++ 在释放向量元素占用的内存时遇到问题
【发布时间】:2018-07-09 17:33:31
【问题描述】:

所以问题是,当我尝试将非动态 obj 推送到 playerList 或尝试删除 n 时,我得到 segfault(核心转储)。我假设问题是在 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 的方法只返回一些整数。

【问题讨论】:

  • Run 方法看起来很奇怪。它有内存泄漏。为什么要分配任何新内存?什么是勇士?请发minimal reproducible example
  • 使用 std::shared_ptr
  • 无关:除了删除赋值运算符,还应该删除复制构造函数。
  • 我已经描述了它有内存泄漏,因为我无法使用 delete n。它导致了段错误。
  • 停止使用原始指针,所有这些问题都会消失

标签: c++ vector segmentation-fault


【解决方案1】:
std::vector<Player>playerList;

应该是

std::vector<Player*>playerList;

如果您想动态分配它们。另一种方法是放置每个元素而不使用新元素。

使用 new 时,您是在堆上分配,但您通过传递堆上分配的值在向量中创建一个新元素。而且你有一个悬空指针(内存泄漏)

如果您使用指针向量,请记住在销毁向量时释放所有元素。

另一种方法是:

 std::vector<std::unique_ptr<Player> >playerList;

这将解决分配问题。

【讨论】:

  • std::vector&lt;Player*&gt;playerList 与解除分配正常工作。但是,当我尝试 Player n = Player()playerList.emplace_back(n) 时,它仍然会导致错误。
  • 可以直接使用playerList.emplace_back();,不需要再创建其他对象。
  • @user4581301 我知道智能指针,但是在这种情况下,我遇到了矢量问题,它的逻辑。
  • @VictorPadureanu 哦,这就是它的工作原理。非常感谢!但是是否有可能将非动态对象传递给向量? !编辑没关系。我仍然可以通过 emplace 传递参数。
  • 是的,你可以。但是您需要为要使用的类编写一个复制构造函数。您还需要删除分配的对象。玩家 n = 玩家(); playerList.push_back(n); / 播放器* n = 新播放器(); playerList.push_back(*n);删除 n;
【解决方案2】:

std::vector的主要工作是为你动态分配对象,所以你不需要使用newdelete

void Helper::Run(){
    playerList.push_back(Player());
}

这将默认构造一个新的玩家对象并将其添加到向量中。

【讨论】:

    【解决方案3】:

    解决方案

    是的,可以通过存储指向Player 的指针来解决问题,但这不必要地增加了由于必须将指向IClass 的指针存储在Player 中而导致的问题。

    相反,您可以通过制作来解决整个问题

    IClass* typeOfClass;
    

    进入

    std::unique_ptr<IClass> typeOfClass;
    

    std::shared_ptr<IClass> typeOfClass;
    

    并使用

    playerList.emplace_back();
    

    Helper::Run.

    TL;DR

    std::vector 做了很多复制和销毁副本。为此,vector 包含的对象必须符合 Rules of Three, Five, or Zero. 之一

    因为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 而无需两个Players 共享相同的数据,Helper::Run 看起来像

    void Helper::Run(){
        playerList.emplace_back();
    }
    

    不会有内存泄漏,使用的指针也会更少。

    但是...您真正想要的是尽可能少地进行特殊处理。每一种特殊情况都意味着更多的测试。如果您可以从源头保护typeOfClass,那么Player 可以像帖子一样愚蠢并遵守零规则。

    在这种情况下,这意味着使用智能指针之类的代理对象来获取typeOfClass 的所有权并为您管理其生命周期。如果所有勇士都可以拥有相同的Warrior 实例,则意味着std:shared_ptr。如果 Warriors 需要单独的 Warrior 实例来处理包含 Player 实例的簿记,您需要一个 unique_ptr

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      相关资源
      最近更新 更多