Manejo de excepciones de C ++
Recibo una excepción y necesito tu revisión
La función principal se encuentra debajo de intentar asignar memoria varias veces y luego lanzar la excepción en el nivel superior.
#include <iostream>
#include <memory>
struct MiserlinessClass{
char * pMemory;
int memory_len;
const int max_size = 10;
MiserlinessClass(int len){
if (len>max_size){
std::cout<<"What a lavish lifestyle! Get out of my face! \n";
std::bad_alloc exception;
throw exception;
}
pMemory = (char *)malloc(len*sizeof(char));
memory_len = len;
}
};
int main(int argc, char** argv){
int len = (argc==2)? strtol(argv[1],NULL,10): 5;
std::unique_ptr<MiserlinessClass> objPtr;
bool allocated = false;
const int max_cnt = 5;
int cnt = 0;
while (!allocated){
try{
std::cout<<"Trying to allocate "<<len<<" chars...\n";
objPtr.reset(new MiserlinessClass(len));
allocated = true;
} catch (std::bad_alloc &e){
len = len >> 1;
cnt++;
if (cnt==max_cnt){
std::cout<<"I give up \n";
throw e;
}
}
}
std::cout<< "Allocated " << objPtr->memory_len << " chars \n";
return 0;
}
Y aquí están los resultados de 3 carreras diferentes
1: asigna memoria desde el primer intento,
2: lo intenta pocas veces y asigna disponible y T
3 - falló después de N intentos y lanza una excepción al nivel superior
---------------------------------------
$ make; ./01_exception_pointer 3 make: Nothing to be done for 'all'. Trying to allocate 3 chars... Allocated 3 chars --------------------------------------- $ make; ./01_exception_pointer 64
make: Nothing to be done for 'all'.
Trying to allocate 64 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 32 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 16 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 8 chars...
Allocated 8 chars
---------------------------------------
$ make; ./01_exception_pointer 1023
make: Nothing to be done for 'all'.
Trying to allocate 1023 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 511 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 255 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 127 chars...
What a lavish lifestyle! Get out of my face!
Trying to allocate 63 chars...
What a lavish lifestyle! Get out of my face!
I give up
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted (core dumped)
Mis preguntas son
Conozco la diferencia formal entre devolver un valor y lanzar la excepción, pero aún no puedo sentir si es hora de entrar en pánico y lanzar una excepción o si el programa debe mantener la calma y simplemente devolver el código de error.
¿Es una forma adecuada de manejar la excepción bad_alloc?
Cualquier comentario o sugerencia adicional :)
Respuestas
Conozco la diferencia formal entre devolver un valor y lanzar la excepción, pero aún no puedo sentir si es hora de entrar en pánico y lanzar una excepción o si el programa debe mantener la calma y simplemente devolver el código de error.
Reglas básicas simples.
Si no puede solucionar el problema localmente, genere una excepción.
No puede resolver el problema de no tener suficiente memoria localmente, así que lanza una excepción. Esto permitirá que la pila para desenrollar la memoria se libere y "Puede" llegar a un punto en el que esto se pueda resolver (o si no, dejar que la aplicación salga). Un ejemplo de dónde se puede detener es cuando está creando una tarea independiente y una de estas tareas falla. No significa que todas las tareas fallarán. Registre el hecho de que esta tarea falló, permita que la excepción libere toda la memoria que usó y luego intente la siguiente tarea.El código de error no debe cruzar los límites de la interfaz.
Los códigos de error son excelentes si los marca. Permite que un mecanismo simple devuelva información uno o dos niveles sin complicar el código. Entonces, si está escribiendo una biblioteca e internamente usa códigos de error, está bien (porque estará bien y verificará todos los códigos de error). Pero no puede confiar en los usuarios de su biblioteca, por lo que si un error se va a propagar fuera de su biblioteca, use una excepción para obligar al usuario a obtenerlo explícitamente.Entrada de usuario y flujos. No use excepciones. La entrada del usuario siempre será propensa a errores y el código que maneja la entrada del usuario necesitará muchas comprobaciones de validación (si se hace correctamente). Ellos saben esto. Entonces, las operaciones en la transmisión simplemente configuran la transmisión en mal estado.
¿Es una forma adecuada de manejar la excepción bad_alloc?
Deje que se propague a la parte superior de su aplicación. Registre algo para que el usuario sepa que sucedió. Si se trata de una tarea independiente, deje la tarea y comience la siguiente. Si esto es solo una parte normal de la ejecución, deje que la aplicación salga.
Cualquier comentario o sugerencia adicional :)
- No use
malloc()/free()en código C ++. - Por cada nuevo debería haber una eliminación.
- Prefiere usar en
make_unique()lugar denew(para ayudar con nuevas coincidencias / eliminar).
Revisión de código:
// Owned pointers are a bad idea.
struct MiserlinessClass{
char * pMemory; // This is an owned pointer.
// At lot of extra work needs to be done here
// You need to look up the rule of three/five
std::bad_alloc exception;
throw exception;
Más fácil de escribir simplemente:
throw std::bad_alloc;
Esta no es una situación de bad_alloc.
if (len>max_size){
std::cout<<"What a lavish lifestyle! Get out of my face! \n";
std::bad_alloc exception;
throw exception;
}
bad_alloc significa que el sistema no pudo asignar memoria debido a la presión de la memoria.
Debe usar: std::range_errorel parámetro de entrada estaba fuera de rango.
No use mallocuse newaquí.
pMemory = (char *)malloc(len*sizeof(char));
// Better:
pMemory = new char[len];
Nota: todavía necesita implementar la regla de tres aquí.
Nota: Sí, debería lanzar una excepción anterior. No desea permitir que el usuario cree objetos no válidos. Esto debe solucionarse antes de que la aplicación entre en producción, por lo que forzar la prueba unitaria a fallar con una excepción es la solución correcta.
No hay ningún destructor para la clase MiserlinessClass. Entonces, la memoria asignada con malloc()se filtrará. Obligándote a quedarte sin memoria más rápido.
Ver Regla de tres.
Nota: Esto liberará el objeto anterior. Pero solo si el nuevo objeto se crea correctamente. Entonces tienes un montón de memoria asignada. Luego, intenta asignar el doble. Si esa asignación funciona, libera la memoria anterior.
objPtr.reset(new MiserlinessClass(len));
Movería la declaración:
std::unique_ptr<MiserlinessClass> objPtr;
en el bloque try haga la asignación allí.
try{
std::cout<<"Trying to allocate "<<len<<" chars...\n";
std::unique_ptr<MiserlinessClass> objPtr = std::make_unique<MiserlinessClass>(len);
allocated = true;
} catch (std::bad_alloc &e){
...
Ahora. La memoria se asigna y luego se libera al final del intento. La próxima vez en el ciclo, sabrá que tiene una tienda limpia para asignar, ya que todo se limpió desde su último intento.
Puede ir un paso más allá y simplemente eliminar el unique_ptr.
try{
std::cout<<"Trying to allocate "<<len<<" chars...\n";
MiserlinessClass objPtr(len);
allocated = true;
} catch (std::bad_alloc &e){
...
¿Por qué no multiplicar por 2?
len = len >> 1;
¿O es eso dividir por 2. De cualquier manera, la intención no está clara? Utilice un código que exprese su intención con claridad.
Solo imprime si no hay excepción.
std::cout<< "Allocated " << objPtr->memory_len << " chars \n";