studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones c++ exception memory-leaks constructor

c++ - para - manual de programacion android pdf



¿Quién borra la memoria asignada durante una operación "nueva" que tiene una excepción en el constructor? (8)

Citado de C ++ FAQ ( parashift.com ):

[17.4] ¿Cómo debo manejar los recursos si mis constructores pueden lanzar excepciones?

Cada miembro de datos dentro de tu objeto debería limpiar su propio desorden.

Si un constructor lanza una excepción, el destructor del objeto no se ejecuta. Si su objeto ya ha hecho algo que debe deshacerse (como asignar algo de memoria, abrir un archivo o bloquear un semáforo), este "elemento que debe deshacerse" debe ser recordado por un miembro de datos dentro del objeto.

Por ejemplo, en lugar de asignar memoria a un miembro de datos Fred* en bruto, coloque la memoria asignada en un objeto miembro "puntero inteligente", y el destructor de este puntero inteligente delete el objeto Fred cuando el puntero inteligente muera. La plantilla std::auto_ptr es un ejemplo de "puntero inteligente". También puede escribir su propio puntero inteligente de conteo de referencia . También puede usar punteros inteligentes para "apuntar" a registros de disco u objetos en otras máquinas .

Por cierto, si crees que tu clase Fred se asignará a un puntero inteligente, sé amable con tus usuarios y crea un typedef dentro de tu clase Fred :

#include <memory> class Fred { public: typedef std::auto_ptr<Fred> Ptr; ... };

Ese typedef simplifica la sintaxis de todo el código que usa tus objetos: tus usuarios pueden decir Fred::Ptr lugar de std::auto_ptr<Fred> :

#include "Fred.h" void f(std::auto_ptr<Fred> p); // explicit but verbose void f(Fred::Ptr p); // simpler void g() { std::auto_ptr<Fred> p1( new Fred() ); // explicit but verbose Fred::Ptr p2( new Fred() ); // simpler ... }

Realmente no puedo creer que no haya podido encontrar una respuesta clara a esto ...

¿Cómo se libera la memoria asignada después de que un constructor de clase C ++ arroja una excepción, en el caso en que se inicializa con el new operador? P.ej:

class Blah { public: Blah() { throw "oops"; } }; void main() { Blah* b = NULL; try { b = new Blah(); } catch (...) { // What now? } }

Cuando probé esto, b es NULL en el bloque catch (lo cual tiene sentido).

Al depurar, noté que el conrol ingresa a la rutina de asignación de memoria ANTES de que golpee al constructor.

Esto en el sitio web de MSDN parece confirmar esto :

Cuando se usa nuevo para asignar memoria para un objeto de clase C ++, se llama al constructor del objeto después de asignar la memoria.

Entonces, teniendo en cuenta que la variable local b nunca se asigna (es decir, es NULL en el bloque catch), ¿cómo se elimina la memoria asignada?

También sería bueno obtener una respuesta de plataforma cruzada sobre esto. es decir, ¿qué dice la especificación C ++?

ACLARACIÓN: No estoy hablando del caso en el que la clase ha asignado memoria en el administrador y luego lanza. Aprecio que en esos casos no se llame al d''tor. Estoy hablando de la memoria utilizada para asignar el objeto ( Blah en mi caso).


Creo que es bastante extraño que un constructor genere una excepción. ¿Podrías tener un valor de retorno y probarlo en tu principal?

class Blah { public: Blah() { if Error { this.Error = "oops"; } } }; void main() { Blah* b = NULL; b = new Blah(); if (b.Error == "oops") { delete (b); b = NULL; }


De C ++ 2003 Standard 5.3.4 / 17 - Nuevo:

Si cualquier parte de la inicialización del objeto descrita arriba termina lanzando una excepción y se puede encontrar una función de desasignación adecuada, se llama a la función de desasignación para liberar la memoria en la que se estaba construyendo el objeto, después de lo cual la excepción continúa propagándose en el contexto de la nueva expresión Si no se puede encontrar una función de desasignación de asignación que no sea ambigua, la propagación de la excepción no libera la memoria del objeto. [Nota: Esto es apropiado cuando la función de asignación llamada no asigna memoria; de lo contrario, es probable que se produzca una pérdida de memoria. ]

Entonces puede haber o no una fuga, depende de si se puede encontrar un separador apropiado (que normalmente es el caso, a menos que se haya anulado el operador nuevo / eliminar). En el caso de que haya un separador apropiado, el compilador es responsable para el cableado en una llamada si el constructor lanza.

Tenga en cuenta que esto está más o menos relacionado con lo que sucede con los recursos adquiridos en el constructor, que es lo que mi primer intento de respuesta se discutió, y es una cuestión que se debate en muchas preguntas frecuentes, artículos y publicaciones.


Debería consultar las preguntas similares here y here . Básicamente, si el constructor lanza una excepción, está seguro de que la memoria del objeto se libera de nuevo. Aunque, si se ha reclamado otra memoria durante el constructor, está solo para liberarla antes de abandonar el constructor con la excepción.

Para su pregunta QUIÉN borra la memoria, la respuesta es el código detrás del operador nuevo (que es generado por el compilador). Si reconoce una excepción que abandona el constructor, debe invocar a todos los destructores de los miembros de la clase (ya que se construyeron con éxito antes de llamar al código del constructor) y liberar su memoria (podría hacerse recursivamente junto con la llamada al destructor, probablemente llamando a una eliminación adecuada en ellos), así como liberar la memoria asignada para esta clase. Luego tiene que volver a lanzar la excepción atrapada del constructor al llamador de nuevo . Por supuesto, puede haber más trabajo que hacer, pero no puedo sacar todos los detalles de mi cabeza porque están a la altura de la implementación de cada compilador.


El problema descrito es tan antiguo como el camino a Roma, para usar un dicho holandés. He resuelto el problema y una asignación de memoria para un objeto que podría lanzar una excepción se ve de la siguiente manera:

try { std::string *l_string = (_heap_cleanup_tpl<std::string>(&l_string), new std::string(0xf0000000, '' '')); delete l_string; } catch(std::exception &) { }

Antes de la llamada real al new operador, se crea un objeto anónimo (temporal) que recibe la dirección de la memoria asignada a través de un nuevo operador definido por el usuario (vea el resto de esta respuesta). En el caso de la ejecución normal del programa, el objeto temporal pasa el resultado del operador nuevo (el objeto recién creado y completamente construido, en nuestro caso una cadena muy muy larga) a la variable l_string . En caso de una excepción, el valor no se transfiere, pero el destructor del objeto temporal borra la memoria (sin por supuesto llamar al destructor del objeto principal).

Es una forma un poco confusa de tratar con el problema, pero funciona. Pueden surgir problemas debido a que esta solución requiere que un nuevo operador definido por el usuario y un operador de eliminación definido por el usuario vayan a la par con él. Los nuevos / delete-operators definidos por el usuario tendrían que llamar a la implementación de la librería estándar de C ++ de los operadores nuevos / eliminar, pero lo dejé por brevedad y me basé en malloc() y free() lugar.

No es la respuesta final, pero creo que vale la pena trabajar en esto.

PD: Hubo una característica ''no documentada'' en el código a continuación, así que hice una mejora.

El código para el objeto temporal es el siguiente:

class _heap_cleanup_helper { public: _heap_cleanup_helper(void **p_heap_block) : m_heap_block(p_heap_block), m_previous(m_last), m_guard_block(NULL) { *m_heap_block = NULL; m_last = this; } ~_heap_cleanup_helper() { if (*m_heap_block == NULL) operator delete(m_guard_block); m_last = m_previous; } void **m_heap_block, *m_guard_block; _heap_cleanup_helper *m_previous; static _heap_cleanup_helper *m_last; }; _heap_cleanup_helper *_heap_cleanup_helper::m_last; template <typename p_alloc_type> class _heap_cleanup_tpl : public _heap_cleanup_helper { public: _heap_cleanup_tpl(p_alloc_type **p_heap_block) : _heap_cleanup_helper((void **)p_heap_block) { } };

El nuevo operador definido por el usuario es el siguiente:

void *operator new (size_t p_cbytes) { void *l_retval = malloc(p_cbytes); if ( l_retval != NULL && *_heap_cleanup_helper::m_last->m_heap_block == NULL && _heap_cleanup_helper::m_last->m_guard_block == NULL ) { _heap_cleanup_helper::m_last->m_guard_block = l_retval; } if (p_cbytes != 0 && l_retval == NULL) throw std::bad_alloc(); return l_retval; } void operator delete(void *p_buffer) { if (p_buffer != NULL) free(p_buffer); }


En resumidas cuentas, si no ha realizado asignaciones de otras entidades en su objeto (como en su ejemplo), la memoria asignada se eliminará automáticamente. Sin embargo, cualquier declaración nueva (o cualquier otra cosa que gestione directamente la memoria) necesita ser manejada en una instrucción catch en el constructor. De lo contrario, el objeto se elimina sin eliminar sus asignaciones posteriores y usted, mi amigo, tiene una fuga.


Si el Constructor arroja, la memoria asignada para el objeto se devuelve automáticamente al sistema.

Tenga en cuenta que no se llamará al destructor de la clase que arrojó.
Pero también se llamará al destructor de cualquier clase base (donde el constructor base ha finalizado).

Nota:
Como la mayoría de las otras personas han notado, los miembros pueden necesitar un poco de limpieza.

Los miembros que se han inicializado completamente tendrán sus destructores llamados, pero si tiene algún puntero RAW que sea suyo (es decir, eliminar en el destructor) tendrá que hacer una limpieza antes de realizar el lanzamiento (otra razón para no usar el propio Punteros RAW en su clase).

#include <iostream> class Base { public: Base() {std::cout << "Create Base/n";} ~Base() {std::cout << "Destroy Base/n";} }; class Deriv: public Base { public: Deriv(int x) {std::cout << "Create Deriv/n";if (x > 0) throw int(x);} ~Deriv() {std::cout << "Destroy Deriv/n";} }; int main() { try { { Deriv d0(0); // All constructors/Destructors called. } { Deriv d1(1); // Base constructor and destructor called. // Derived constructor called (not destructor) } } catch(...) { throw; // Also note here. // If an exception escapes main it is implementation defined // whether the stack is unwound. By catching in main() you force // the stack to unwind to this point. If you can''t handle re-throw // so the system exception handling can provide the appropriate // error handling (such as user messages). } }


Si un objeto no puede completar la destrucción porque el constructor lanza una excepción, lo primero que ocurre (esto sucede como parte del manejo especial del constructor) es que todas las variables miembro que se han construido se destruyen, si se lanza una excepción en la lista de inicializadores , esto significa que solo se destruyen los elementos para los que el iniciador ha completado.

Luego, si el objeto se estaba asignando con new , se llama a la función de desasignación apropiada ( operator delete ) con los mismos argumentos adicionales que se pasaron al operator new . Por ejemplo, new (std::nothrow) SomethingThatThrows() asignará memoria con operator new (size_of_ob, nothrow) , intentará construir SomethingThatThrows , destruirá cualquier miembro que se haya construido correctamente, luego invocará operator delete (ptr_to_obj, nothrow) cuando una excepción se propaga - no perderá memoria.

Lo que debe tener cuidado es asignar varios objetos en sucesión; si uno de los últimos arroja, los anteriores no serán desasignados automáticamente. La mejor forma de evitar esto es con punteros inteligentes, ya que como objetos locales se invocarán sus destructores durante el desenrollado de la pila, y sus destructores desasignarán correctamente la memoria.