c++ - Cómo liberar objetos CWin creados en un dialogo para niños para evitar fugas de memoria
visual-c++ mfc (3)
En CChildDlg
destructor add for(auto img : m_images) delete img;
.
Estoy creando un conrol CStatic en un cuadro de diálogo secundario, que funciona bien. El problema es que después de cerrar el cuadro de diálogo secundario la memoria no se libera correctamente.
Traté de sobrescribir PostNCDestoy
como se describe en este hilo: ¿Es esto una pérdida de memoria en MFC? Pero esto me da una excepción no controlada al llamar a "eliminar esto".
¿Alguna idea de cuál es la forma correcta de lanzar CStatic, CButtons para evitar fugas de memoria?
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);
}
De acuerdo con las recomendaciones en este hilo, cambié el código de la siguiente manera:
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));
}
El código funciona bien, pero la filtración aún está allí. Solo si elimino la línea donde estoy configurando el mapa de bits en el CStatic, la fuga desaparece:
//HBITMAP hbmOld = img->SetBitmap(imgFromFile.Detach());
//if (hbmOld != nullptr){
//::DeleteObject(hbmOld);
//}
Por lo tanto, tiene que estar relacionado con hacerse cargo de la propiedad del CImage al CStatic de alguna manera. Estoy cargando hasta 100 imágenes en el cuadro de diálogo. Por cada apertura del diálogo todavía puedo ver un aumento significativo de la memoria, que no cae después de cerrarla.
¿Alguna otra sugerencia de lo que podría estar mal de perder?
Lo que me di cuenta es que solo al ocuparse de una limpieza adecuada de los HBITMAP
que se devuelven durante la llamada .Detach()
el número de objetos GDI está disminuyendo a un valor correcto y la pérdida de memoria desaparece.
CChildDlg.h
HBITMAT m_deleteMeWhenClosingDlg;
....
CChildDlg.cpp
...
m_deleteMeWhenClosingDlg = imgFromFile.Detach();
HBITMAP hbmOld = img->SetBitmap(m_deleteMeWhenClosingDlg);
...
Más tarde en OnDestroy () por ejemplo
::DeleteObject(m_deleteMeWhenClosingDlg)
La solución ingenua sería simplemente iterar a través de su clase de contenedor, llamando a delete
en cada puntero. Algo como:
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
Si hace esto en su destructor o en respuesta al mensaje WM_DESTROY
( OnDestroy
en MFC), se asegurará de que se CStatic
todas sus instancias CStatic
, solucionando el problema de pérdida de memoria.
Pero esta no es la mejor solución. En C ++, debe aprovechar Scope-Bound Resource Management (SBRM) , también conocido como RAII. Esto implica el uso de funciones de idioma para limpiar objetos automáticamente , en lugar de tener que hacerlo manualmente. Esto no solo hace que el código sea más limpio, sino que garantiza que nunca lo olvides, que tu código es totalmente seguro para excepciones y puede incluso ser más eficiente.
Normalmente, solo almacenaría los objetos en la clase contenedora. Es decir, en lugar de std::vector<CStatic*>
, solo tendrías std::vector<CStatic>
. De esta forma, cada vez que se destruye el contenedor vectorial (lo que ocurre automáticamente cuando se sale del alcance, gracias a SBRM), todos los objetos que contiene se destruyen también ( es decir , sus destructores son llamados automáticamente).
Sin embargo, los contenedores de biblioteca estándar requieren que los objetos se puedan copiar o mover. Las clases de MFC derivadas de CObject
no se pueden copiar, y presumiblemente tampoco son móviles (dada la edad de MFC y las expresiones idiomáticas estándar para hacer que un objeto no copiable implícitamente lo haga no movible). Eso significa que esto no funcionará: no puede almacenar objetos CStatic
en un vector u otra clase de contenedor.
Afortunadamente, el C ++ moderno tiene una solución para este problema: el puntero inteligente . Los indicadores inteligentes son exactamente lo que parecen: una clase que envuelve el puntero desnudo ("tonto"), dándole superpoderes. El jefe entre los inteligentes ofrecidos por un puntero inteligente es SBRM. Cada vez que el objeto del puntero inteligente se destruye, elimina automáticamente su puntero tonto subyacente. Esto le libera a usted, el humilde programador, de tener que escribir una sola declaración de delete
. De hecho, dos cosas que casi nunca tendrías que hacer en C ++ son:
- escribir explícitamente
delete
- usar punteros crudos
Así que mi recomendación, dadas las limitaciones de las clases derivadas CObject
de MFC, es almacenar punteros inteligentes en su vector. La sintaxis no es bonita, pero se resuelve trivialmente con un typedef:
typedef std::vector<std::unique_ptr<CStatic>> CStaticVector; // for C++11
(Para C ++ 03, dado que std::auto_ptr
no se puede usar en un contenedor estándar [de nuevo, porque no se puede copiar], necesitaría usar un puntero inteligente diferente, como la biblioteca Boost Smart Pointers ).
No más administración de memoria manual, no más pérdidas de memoria, no más dolores de cabeza. Trabajar en C ++ literalmente pasa de ser un dolor en el trasero a ser rápido, divertido y eficiente.