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



我正在子对话框中创建一个CStatic控件,它工作得很好。问题是,在关闭子对话框后,内存不能正确释放。

我试图覆盖PostNCDestoy在这个线程中描述的:这是一个内存泄漏在MFC但是这给了我一个未处理的异常,通过调用"delete this"。

任何想法的正确方式是释放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个图像到对话框。通过每次打开对话框,我仍然可以看到内存的显著提高,关闭对话框后内存不会下降。

还有其他建议吗?

naïve的解决方案是简单地遍历容器类,在每个指针上调用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++中,您应该利用范围约束资源管理(SBRM),也就是通常所说的RAII。这涉及到使用语言特性来自动清理对象,而不是必须手动执行。这不仅使代码更干净,而且确保您永远不会忘记,您的代码是完全异常安全的,甚至可能更高效。

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

然而,标准库容器要求对象要么是可复制的,要么是可移动的。从CObject派生的MFC类是不可复制的,也可能是不可移动的(考虑到MFC的年龄和使对象不可复制的标准习惯用法隐式地使其不可移动)。这意味着这将不起作用:您不能将CStatic对象本身存储在vector或其他容器类中。

幸运的是,现代c++为这个问题提供了一个解决方案:智能指针。智能指针顾名思义:一个包装裸("哑")指针的类,赋予它超能力。智能指针提供的主要智能是SBRM。只要智能指针对象被销毁,它就会自动删除其底层哑指针。这使您,卑微的程序员,不必编写单个delete语句。事实上,在c++中有两件事你几乎不应该做:

  • 显式写入delete
  • 使用原始指针

因此,考虑到MFC的CObject派生类的限制,我的建议是在向量中存储智能指针。语法不是很漂亮,但是可以用typedef:

简单地解决这个问题。
typedef std::vector<std::unique_ptr<CStatic>> CStaticVector;   // for C++11

(对于c++ 03,由于std::auto_ptr不能在标准容器中使用[再次,因为它是不可复制的],您需要使用不同的智能指针,例如Boost智能指针库)

没有更多的手动内存管理,没有更多的内存泄漏,没有更多的头痛。用c++工作从令人讨厌的事情变成了快速、有趣和高效。

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

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

CChildDlg.h

HBITMAT m_deleteMeWhenClosingDlg;
....

CChildDlg.cpp

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

之后在OnDestroy()中,例如

  ::DeleteObject(m_deleteMeWhenClosingDlg)

相关内容

  • 没有找到相关文章

最新更新