我正在子对话框中创建一个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智能指针库)
在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)