C++ de los fundamentos al dominio — Lección 15
Gestione los recursos de forma segura incluso cuando se produzcan excepciones
Los artículos escritos en este blog fueron creados con el objetivo de enseñar. Además, la información del artículo puede ser incorrecta, así que tenga cuidado.
Si ocurre un problema en el código intermedio después de leer el archivo y asignar memoria, debe limpiar los recursos ya obtenidos. Si escribe manualmente un código de limpieza para cada ruta de retorno y excepción, es fácil pasarlo por alto.
Si ocurre una excepción
Una excepción es una forma de transmitir a la parte que llama un error que no se puede gestionar en la ubicación actual.
Item LoadItem(int id)
{
if (id <= 0)
{
throw std::invalid_argument("id must be positive");
}
return ReadItemFromFile(id);
}
try
{
Item item = LoadItem(inputId);
}
catch (const std::exception& error)
{
std::cerr << error.what() << '\n';
}
Cuando se ejecuta throw, el código debajo de la línea actual se omite y se encuentra un catch coincidente. En este proceso se llama al destructor del objeto local ya creado.
No todos los fracasos requieren excepciones. Los resultados esperados, como un error en la búsqueda de elementos, pueden ser más obvios como opcionales o booleanos.
Organizándose con RAII
Al vincular la vida útil de un recurso con la vida útil de un objeto, no es necesario separar las rutas de retorno normales y de excepción.
std::string ReadConfig(const std::filesystem::path& path)
{
std::ifstream file(path);
if (!file)
{
throw std::runtime_error("config open failed");
}
std::string text(
std::istreambuf_iterator<char>{file},
std::istreambuf_iterator<char>{}
);
return text;
}
Incluso si ocurre una excepción porque la creación de una cadena falla en el medio, el destructor de ifstream cierra el archivo. Uniqueptr para objetos dinámicos, Uniqueptr para cerradurasstd::lock_guard, la memoria contenedora juega el mismo papel que el vector.
void UpdateInventory()
{
std::lock_guard<std::mutex> lock(inventoryMutex);
RebuildInventory();
}
Incluso si se produce una excepción en RebuildInventory, el destructor de bloqueo libera el mutex. Reduce los errores que pueden ocurrir al emparejar directamente el bloqueo y el desbloqueo.
seguridad excepcional
Debe pensar qué estado garantiza su función cuando ocurre una excepción.
- Garantía básica: no hay fugas de recursos y los objetos siguen siendo utilizables.
- Garantía sólida: si la operación falla, se mantiene el estado anterior a la llamada.
- Sin excepciones garantizadas: la función no genera ninguna excepción.
Es fácil crear garantías sólidas completando el nuevo estado en un objeto temporal y reemplazándolo al final.
void Inventory::ReplaceItems(std::vector<Item> newItems)
{
items.swap(newItems);
}
Si hay un error al crear argumentos de llamada, los elementos existentes no se cambiarán. Dentro de la función, la nueva lista completada por swap reemplaza la lista existente.
noexcept
Para funciones que no arrojan excepciones,noexceptse puede mostrar.
Player(Player&& other) noexcept = default;
Player& operator=(Player&& other) noexcept = default;
Un vector determina si se puede mover de forma segura al reasignar elementos. Si el constructor de movimientos no es excepción, es probable que elija mover en lugar de copiar.
Sin embargo, puede ocurrir una excepción internamente, pero si agrega noexcept, en el momento en que desaparece la excepción,std::terminatese llama. No es una palabra clave agregada a ciegas para mejorar el rendimiento, sino una promesa de no generar una excepción.
La clave para una seguridad excepcional es no escribir muchas capturas. Los recursos son manejados por objetos RAII y las tareas se organizan en un orden que no destruye el estado existente incluso si falla.