如何释放在子对话框中创建的 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<CStatic>
,而不是std::vector<CStatic*>
。这样,每当向量容器被销毁时(由于 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()
时对返回的 HBITMAP
s 进行适当的清理,GDI 对象的数量才会减少到正确的值,并且内存泄漏就会消失。
CChildDlg.h
HBITMAT m_deleteMeWhenClosingDlg;
....
CChildDlg.cpp
...
m_deleteMeWhenClosingDlg = imgFromFile.Detach();
HBITMAP hbmOld = img->SetBitmap(m_deleteMeWhenClosingDlg);
...
例如稍后在 OnDestroy() 中
::DeleteObject(m_deleteMeWhenClosingDlg)
【讨论】:
以上是关于如何释放在子对话框中创建的 CWin 对象以避免内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
如何使在 ddply 中创建的对象在函数外部可用(在全局环境中)?