c++ - what - ¿Debería liberar una matriz si la asignación de esa matriz arrojó una excepción?
try catch++ (3)
Tengo una clase potencialmente inestable que alguien más escribió, y tengo que crear una matriz de objetos de esa clase. Mencioné que la clase es inestable, por lo que ocasionalmente puede generar una excepción en el constructor predeterminado. No tengo acceso al código fuente, solo a los binarios compilados.
Cuando asigno la matriz dinámica de este tipo de objetos utilizando new
, existe la posibilidad de que uno de estos objetos defectuosos pueda lanzar una excepción. Está lanzando una excepción personalizada, no std::bad_alloc
. De todos modos, necesito hacer que el programa se recupere de la excepción y simplemente continuar con la configuración, aunque con algunos indicadores de error y qué no. Creo que debería delete
la memoria asociada con la matriz para evitar una pérdida de memoria.
Mi razonamiento es que si la clase lanza una excepción construyendo un elemento en algún lugar en medio de la matriz, ese elemento no se construirá correctamente, y todos los elementos futuros se detendrán construyendo por la excepción, pero los elementos anteriores habrán sido debidamente construido desde que sucedió antes de que se lanzara la excepción. Me pregunto, ¿es una buena idea llamar a delete
en la catch (...) { }
? ¿Cómo voy a resolver esta pérdida de memoria?
Badclass* array = nullptr;
try {
array = new Badclass[10]; // May throw exceptions!
} catch (...) {
delete[] array;
array = nullptr;
// set error flags
}
Esta es la forma en que visualizo esto en la memoria. ¿Es esto correcto?
array 0 1 2 3 4 5 6 7 8 9
___ __________________________________
| ---------->| :) | :) | :) | :) | :( | | | | | |
|___| |____|____|____|____|____|_|_|_|_|_|
En la siguiente línea de código:
array = new Badclass[10];
new Badclass[10]
se evalúa primero. Si eso produce una excepción, la ejecución no alcanza la asignación. array
conserva su valor anterior que es nullptr
. No tiene efecto invocar delete en un nullptr
.
La pregunta de la sección de comentarios:
¿Este tipo de comportamiento se basa en el mismo principio que el desenrollado de pila?
La sección sobre "Manejo de excepciones" en la norma nos ayuda a comprender qué sucede cuando se lanza una excepción en el momento de la asignación.
18 Manejo de excepciones [excepto]
...
18.2 Constructores y destructores [except.ctor]1. A medida que el control pasa desde el punto en que se lanza una excepción a un controlador, los destructores son invocados por un proceso , especificado en esta subcláusula, llamado desenrollado de la pila .
...
3.Si la inicialización o destrucción de un objeto que no sea mediante la delegación del constructor se termina por una excepción, se invoca el destructor para cada uno de los subobjetos directos del objeto y, para un objeto completo, subobjetos de la clase base virtual, cuya inicialización ha finalizado y destructor aún no ha comenzado la ejecución, excepto que en el caso de destrucción, los miembros variantes de una clase de tipo sindical no se destruyen. Los subobjetos se destruyen en el orden inverso de la finalización de su construcción . Dicha destrucción se secuencia antes de ingresar a un controlador de la función-try-block del constructor o destructor, si corresponde.
No es necesario llamar a eliminar en caso de excepción en:
array = new Badclass[10]; // May throw exceptions!
No hay pérdida de memoria.
Como referencia lea acerca de la nueva expresión en cppreference :
Si la inicialización termina lanzando una excepción (p. Ej., Desde el constructor), si new-expression asigna cualquier almacenamiento, llama a la función de desasignación adecuada: eliminar operador para el tipo que no es de matriz, eliminar operador [] para el tipo de matriz .
Por lo tanto, claramente indica que delete[]
se llama automáticamente, y que no tiene que llamarlo.
Si parte de los objetos se construyeron con new[]
antes de que se lanzara la excepción, todos los objetos que se hayan construido se destruirán antes de que se libere la memoria. Esto es igual que con la construcción de objetos que contiene una matriz, y se lanza una excepción al construir algún objeto en la matriz.
Para contestar la pregunta final:
¿Cómo voy a resolver esta pérdida de memoria?
No hay pérdida de memoria. La filtración solo ocurriría si BadClass
asignara dinámicamente el contenido y nunca lo liberara en su destructor. Ya que no nos damos cuenta de su implementación de BadClass
, y no en el negocio de adivinar, eso depende de usted. La única forma de new BadClass[N];
la pérdida de memoria en sí misma es si se completa y luego se descarta la única referencia que está administrando manualmente ( array
).
Una matriz asignada dinámicamente, lanzando dentro de uno de los constructores para los elementos en ella, (a) hará retroceder a los destructores en orden opuesto a los elementos ya construidos, (b) liberará la memoria asignada, y finalmente (c) oficiará el lanzamiento real al el controlador de captura más cercano (o el controlador predeterminado cuando no hay ninguno).
Debido a que el lanzamiento se produce, la asignación al puntero de la matriz resultante nunca transpira, y por lo tanto no necesita delete[]
.
Mejor demostrado por ejemplo:
#include <iostream>
struct A
{
static int count;
int n;
A() : n(++count)
{
std::cout << "constructing " << n << ''/n'';
if (count >= 5)
throw std::runtime_error("oops");
}
~A()
{
std::cout << "destroying " << n << ''/n'';
}
};
int A::count;
int main()
{
A *ar = nullptr;
try
{
ar = new A[10];
}
catch(std::exception const& ex)
{
std::cerr << ex.what() << ''/n'';
}
}
Salida
constructing 1
constructing 2
constructing 3
constructing 4
constructing 5
destroying 4
destroying 3
destroying 2
destroying 1
oops
Tenga en cuenta que debido a que la construcción del elemento ''5'' nunca se completó, su destructor no se dispara. Sin embargo, los miembros que se construyeron con éxito están destruidos (no se muestra en el ejemplo anterior, pero es un ejercicio divertido si lo desea).
Todo lo dicho, use punteros inteligentes independientemente.