如何释放在子对话框中创建的 CWin 对象以避免内存泄漏

Posted

技术标签:

【中文标题】如何释放在子对话框中创建的 CWin 对象以避免内存泄漏【英文标题】:How to free CWin objects created in a child diaolog to avoid memory leaks 【发布时间】:2016-06-30 09:23:27 【问题描述】:

我在子对话框中创建了一个 CStatic 控件,效果很好。问题是关闭子对话框后内存没有正确释放。

我试图覆盖PostNCDestoy,就像这个线程中描述的那样: Is this a memory leak in MFC 但这通过调用“删除这个”给了我一个未处理的异常。

知道释放 CStatic、CButtons 以避免内存泄漏的正确方法是什么吗?

CChildDlg.h

class CChildDlg

  std::vector<CStatic*> m_images;
  void addNewImage(const int &xPos, const int &yPos)
  ...

CChildDlg.cpp

void CChildDlg::addNewImage(const int &xPos, const int &yPos)

  CImage imgFromFile;
  CStatic *img = new CStatic;

  imgFromFile.Load(_T("someImg.jpg"));
  int width = imgFromFile.GetWidth();
  int height = imgFromFile.GetHeight();


  img->Create(_T("Image"), WS_CHILD | WS_VISIBLE | SS_BITMAP,
    CRect(xPos, yPos, xPos + width, yPos + height), this, 10910);

  HBITMAP hbmOld = img->SetBitmap(imgFromFile.Detach());
  if (hbmOld != nullptr)
    ::DeleteObject(hbmOld);
  
  m_images.pushback(img);

根据此线程中的建议,我更改了如下代码:

CChildDlg.h

class CChildDlg

private:
  typedef std::vector<std::unique_ptr <CStatic>> CStaticImgs;
  CStaticImgs m_images;
  void addNewImage(const int &xPos, const int &yPos)
  ...

CChildDlg.cpp

void CChildDlg::addNewImage(const int &xPos, const int &yPos)

  CImage imgFromFile;
  std::unique_ptr<CStatic> img(new CStatic);

  imgFromFile.Load(_T("someImg.jpg"));
  int width = imgFromFile.GetWidth();
  int height = imgFromFile.GetHeight();

  img->Create(_T("Image"), WS_CHILD | WS_VISIBLE | SS_BITMAP,
     CRect(xPos, yPos, xPos + width, yPos + height), this, 10910);

  HBITMAP hbmOld = img->SetBitmap(imgFromFile.Detach());
  if (hbmOld != nullptr)
     ::DeleteObject(hbmOld);
  
  m_images.pushback(std::move(img));

代码运行良好,但泄漏仍然存在。仅当我删除将位图设置为 CStatic 的行时,泄漏才会消失:

//HBITMAP hbmOld = img->SetBitmap(imgFromFile.Detach());
//if (hbmOld != nullptr)
  //::DeleteObject(hbmOld);
//

因此,它必须与以某种方式将 CImage 的所有权接管给 CStatic 有关。我正在向对话框加载多达 100 张图像。每次打开对话框时,我仍然可以看到内存显着增加,关闭后内存不会下降。

任何其他建议可能会丢失什么?

【问题讨论】:

不要动态创建CStatic,作为成员放入CChildDlg。 我的 CStatic 对象数量只有在运行时才知道。 你确定你有内存泄漏吗?您是否在子窗口的析构函数中进行了调试和设置并且没有命中断点(将任何自定义窗口添加到您的窗口以进行测试)?您引用的答案是关于无模式对话框窗口,而不是主框架的子窗口(发帖人特别将其排除在他的答案之外。) 您的容器类应该存储对象本身,而不是指向它们的指针。然后,当容器超出范围时(即,当调用类的析构函数时),它将自动释放它包含的所有 CStatic 对象。 嗯,好点子。虽然可复制在技术上不是标准容器的要求,但我想这些 MFC 类也不是可移动的。然后使用智能指针。制作std::unique_ptr对象的向量,一旦向量超出范围,它将通过指针自动删除CStatic对象。 (基本点是,利用SBRM 和现代C++ 习惯用法,而不是手动调用delete。) 【参考方案1】:

简单的解决方案是简单地遍历您的容器类,在每个指针上调用delete。比如:

for (auto i : m_images)                        delete i;             // on C++11
for (size_t i = 0; i < m_images.size(); ++i)   delete m_images[i];   // on C++03

如果您在析构函数中执行此操作或响应WM_DESTROY 消息(MFC 中的OnDestroy),它将确保您的每个CStatic 实例都被销毁,从而解决内存泄漏问题。

但这不是最好的解决方案。在 C++ 中,您应该利用 Scope-Bound Resource Management (SBRM),通常也称为 RAII。这涉及使用语言功能自动清理对象,而不必手动进行。这不仅使代码更简洁,而且确保您永远不会忘记,您的代码是完全异常安全的,甚至可能更高效。

通常,您只需将对象本身存储在容器类中。也就是说,您将只有std::vector&lt;CStatic&gt;,而不是std::vector&lt;CStatic*&gt;。这样,每当向量容器被销毁时(由于 SBRM,当它超出范围时会自动发生),它包含的所有对象也会被销毁(,它们的析构函数被调用自动)。

但是,标准库容器要求对象是可复制的或可移动的。从CObject 派生的 MFC 类不可复制,并且可能也不可移动(考虑到 MFC 的时代以及使对象不可复制的标准习惯用法隐含地使其不可移动)。这意味着这行不通:您不能将 CStatic 对象本身存储在向量或其他容器类中。

幸运的是,现代 C++ 有一个解决这个问题的方法:smart pointer。智能指针就像它们听起来的样子:一个包装裸(“哑”)指针的类,赋予它超能力。智能指针提供的智能中最主要的是 SBRM。每当智能指针对象被销毁时,它都会自动删除其底层的哑指针。这让你,谦逊的程序员,不必编写单个 delete 语句。事实上,在 C++ 中你几乎不需要做的两件事是:

明确写delete 使用原始指针

因此,鉴于 MFC 的 CObject 派生类的局限性,我的建议是将智能指针存储在向量中。语法不是很漂亮,但可以用 typedef 轻松解决:

typedef std::vector<std::unique_ptr<CStatic>> CStaticVector;   // for C++11

(对于 C++03,由于 std::auto_ptr cannot be used in a standard container [再次,因为它不可复制],您需要使用不同的智能指针,例如 Boost Smart Pointers library。)

没有更多的手动内存管理,没有更多的内存泄漏,没有更多的头痛。使用 C++ 工作从字面上看是从令人头疼的事情变成了快速、有趣和高效的过程。

【讨论】:

std::vector of std::auto_ptr 禁止组合。 @Anatoly 呃,我总是忘记 auto_ptr 是多么愚蠢。感谢您指出这一点! @CodyGray 非常感谢您的详细回答。我更改了我的代码,现在我正在使用智能指针。关于这一点的一个注意事项:std::unique_ptr 也是不可复制的,只是可移动的,非常正确!有关详细信息,请参阅以下线程:***.com/questions/3283778/…。但是,使用智能指针并没有解决我的问题。我会用我的新见解更新我的帖子。【参考方案2】:

CChildDlg析构函数中添加for(auto img : m_images) delete img;

【讨论】:

【参考方案3】:

我意识到,只有在调用 .Detach() 时对返回的 HBITMAPs 进行适当的清理,GDI 对象的数量才会减少到正确的值,并且内存泄漏就会消失。

CChildDlg.h

HBITMAT m_deleteMeWhenClosingDlg;
....

CChildDlg.cpp

  ...
  m_deleteMeWhenClosingDlg = imgFromFile.Detach();
  HBITMAP hbmOld = img->SetBitmap(m_deleteMeWhenClosingDlg);
  ...

例如稍后在 OnDestroy() 中

  ::DeleteObject(m_deleteMeWhenClosingDlg)

【讨论】:

以上是关于如何释放在子对话框中创建的 CWin 对象以避免内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何限制在 VS 设计器中创建的编辑框中的数字

在子线程中创建对话框时无法正确显示

如何使在 ddply 中创建的对象在函数外部可用(在全局环境中)?

无法关闭在 Interface Builder 中创建的 NSView 实例的剪辑

如何调用在另一个函数中创建的对象

你如何调用在javascript中的数组中创建的对象的函数?