【问题标题】:How to avoid this memory leak?如何避免这种内存泄漏?
【发布时间】:2016-08-14 16:13:44
【问题描述】:

这是我的代码:

void MIDITest::CreateNoteBlock() {
    IMidiMsgExt* midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(57, 100, 0, 0, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(60, 100, 0, tickSize * 38, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(62, 100, 0, 0, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(65, 100, 0, tickSize * 32, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(57, 0, tickSize * 111, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(60, 0, tickSize * 111, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(62, 0, tickSize * 75, 0);
    queuedNotes.insert(*midiMessage);

    midiMessage = new IMidiMsgExt;
    midiMessage->MakemidiMessageMsg(65, 0, tickSize * 105, 0);
    queuedNotes.insert(*midiMessage);
}   

所以在每个new 操作符处,它都会分配一块内存。

我应该在 queuedNotes 中的任何 insert 之后使用 free 吗?还是在void函数返回后释放? (即 CreateNoteBlock 的括号)。

或者我可以每次将midiMessage 指针“重用”为新的IMidiMsgExt

【问题讨论】:

  • 向我们展示完整的代码。 queuedNotes 是什么?
  • @Chiel 这是multiset<IMidiMsgExt, IMidiMsgExtComp> queuedNotes;。这就是代码:它在类的 CTOR 中被调用。
  • 如何使用'IMidiMsgExt midiMessage;'而不是 'IMidiMsgExt* midiMessage = new IMidiMsgExt'?
  • 没关系,真的,*midiMessage是传值,也就是做一个拷贝。 queuedNotes 不能取得指针的所有权。所以是的,你必须删除每个新的。
  • 您应该永远在使用new 创建的内容上使用freefreemalloc一起使用;对于new,您必须使用delete。永远不要混合它们!混合它们就像从Avis租一辆汽车然后还给赫兹一样,这简直是错误的!在 C++ 中你不应该使用 mallocfree,只使用 newdelete(撇开智能指针),因为你不只是想分配/回收内存,你还需要构造函数/析构函数运行。请查看stackoverflow.com/questions/240212/…

标签: c++ methods memory-leaks free


【解决方案1】:

答案是根本不使用new。创建具有自动存储期限的对象

IMidiMsgExt midiMessage;

然后您可以继续拨打MakemidiMessageMsg 并将消息副本插入multiset

midiMessage.MakemidiMessageMsg(57, 100, 0, 0, 0);
queuedNotes.insert(midiMessage);
midiMessage.MakemidiMessageMsg(60, 100, 0, tickSize * 38, 0);
queuedNotes.insert(midiMessage);
//...

现在multiset 拥有所有消息的副本,并且在函数结束时midiMessage 被销毁,无需进行内存管理。

如果IMidiMsgExt 有一个类似于MakemidiMessageMsg 的构造函数,您可以在其中构造一个完整的消息,那么您可以进一步简化它并使用类似的东西

queuedNotes.insert(IMidiMsgExt(57, 100, 0, 0, 0));
queuedNotes.insert(IMidiMsgExt(60, 100, 0, tickSize * 38, 0));

现在我们甚至不需要midiMessage

【讨论】:

  • 但是在函数结束时不会“所有”midiMessage(使用new创建)也被销毁吗?
  • @paizza 没有。如果你调用new,释放内存的唯一方法是调用delete。指针被销毁但内存不会自动归还。
  • 是自动存储期限的原因吧?所以你的建议是关于automatic storage durationnew 关于dynamic storage duration stackoverflow.com/questions/6337294/…
  • 我可以使用指针创建automatic storage duration 吗?还是只使用对象本身而不是指针才有效?
  • @paizza 你可以,但这些被称为智能指针,从我在这里看到的内容来看,真的不需要它们,如果你想做的只是创建消息并将它们添加到多集然后假装指针不存在。指针有用途,但在 C++ 中,如果可以使用具有自动存储持续时间的对象,您应该这样做。这几乎就像 0 成本垃圾回收一样。
【解决方案2】:

您是 Java 还是 C# 背景?在 C++ 中,您不必使用 new 来创建对象,只需声明它们即可:

IMidiMsgExt midiMessage;
midiMessage.MakemidiMessageMsg(57, 100, 0, 0, 0);
queuedNotes.insert(midiMessage);

要么是这样(这是我推荐的解决方案),要么你必须显式释放对象:

IMidiMsgExt* midiMessage = new IMidiMsgExt;
midiMessage->MakemidiMessageMsg(57, 100, 0, 0, 0);
queuedNotes.insert(*midiMessage);
delete miniMessage;

【讨论】:

  • 是的,我是一名 C# 程序员。
【解决方案3】:

使用 new 动态分配 IMidiMsgExt 似乎是多余的。您可以直接在堆栈上分配它(没有指针),然后当您的方法返回时它将被销毁。即:

void MIDITest::CreateNoteBlock() {
    IMidiMsgExt midiMessage();
    midiMessage.MakemidiMessageMsg(57, 100, 0, 0, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(60, 100, 0, tickSize * 38, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(62, 100, 0, 0, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(65, 100, 0, tickSize * 32, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(57, 0, tickSize * 111, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(60, 0, tickSize * 111, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(62, 0, tickSize * 75, 0);
    queuedNotes.insert(midiMessage);

    midiMessage = IMidiMsgExt();
    midiMessage.MakemidiMessageMsg(65, 0, tickSize * 105, 0);
    queuedNotes.insert(midiMessage);
}

【讨论】:

    【解决方案4】:

    尝试让你的 API 更像 C++。

    在堆栈上使用一个对象,而不是在堆中创建很多新对象。

    void MIDITest::CreateNoteBlock() {
        IMidiMsgExt midiMessage;
    
        midiMessage.MakemidiMessageMsg(57, 100, 0, 0, 0);
        queuedNotes.insert(midiMessage);
    
        midiMessage.MakemidiMessageMsg(60, 100, 0, tickSize * 38, 0);
        queuedNotes.insert(midiMessage);
    
        // ...
    }
    

    在构造函数中初始化你的对象。为IMidiMsgExt 定义一个operator = (const IMidiMsgExt&)

    void MIDITest::CreateNoteBlock() {
        IMidiMsgExt midiMessage(57, 100, 0, 0, 0);
        queuedNotes.insert(midiMessage);
    
        midiMessage = IMidiMsgExt(60, 100, 0, tickSize * 38, 0);
        queuedNotes.insert(midiMessage);
    
        // ...
    }
    

    我猜,insert() 需要 const IMidiMsgExt&。所以你可以直接传递新初始化的对象:

    void MIDITest::CreateNoteBlock() {
        queuedNotes.insert({57, 100, 0, 0, 0});
        queuedNotes.insert({60, 100, 0, tickSize * 38, 0});
    
        // ...
    }
    

    顺便说一句:您应该更喜欢使用例如std::queue<>queuedNotes。那么你不会使用insert(),而是push(),或者emplace()emplace() 的优点是,它在容器中构造对象,而不是先创建对象再复制到容器中:

    void MIDITest::CreateNoteBlock() {
        queuedNotes.emplace(57, 100, 0, 0, 0);
        queuedNotes.emplace(60, 100, 0, tickSize * 38, 0);
    
        // ...
    }
    

    您的类型名称IMidiMsgExt 也向我表明,您正在尝试模仿 C++ 中的 C# 思维。这是可能的,但通常不是首选解决方案。根据您的问题,我对您的类树和提供建议的基本要求知之甚少,但在 C++ 中,这通常是一种代码味道。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-08
      • 1970-01-01
      • 2020-05-13
      • 1970-01-01
      • 1970-01-01
      • 2018-04-08
      相关资源
      最近更新 更多