ProgrammazioneSviluppatore C++ Backend

Come funziona il sistema di eccezioni in C++ e in cosa si differenzia da altri linguaggi? Come progettare correttamente la gestione delle eccezioni nelle applicazioni industriali?

Supera i colloqui con l'assistente IA Hintsage

Risposta

Il sistema di eccezioni in C++ si basa sul meccanismo try-catch e sulla parola chiave throw. Quando si verifica una situazione eccezionale (ad esempio, un errore di risorsa, violazione di invarianti), all'interno di un blocco try viene generata (lanciata) un'eccezione tramite l'operatore throw. Le operazioni per la ricerca del gestore appropriato (catch) avvengono mostrando lo stack fino al primo tipo corrispondente.

Dettagli importanti:

  • A differenza di Java o C#, in C++ le eccezioni possono essere di qualsiasi tipo (solitamente una classe derivata da std::exception).
  • In C++ non esiste una specifica obbligatoria per le eccezioni (come le eccezioni controllate in Java), invece, in C++11 e oltre è stato introdotto il modificatore noexcept.
  • Quando viene lanciata un'eccezione, vengono chiamati i distruttori per tutti gli oggetti nello stack tra il punto in cui viene generata e il punto in cui viene gestita — è importante mantenere RAII.

Progettazione:

  • Lancia solo eccezioni per errori "inaspettati", per il resto utilizza il codice di ritorno.
  • Cattura per riferimento costante — catch(const std::exception& e).
  • GNU consiglia di lanciare oggetti copiabili e non puntatori (evitare la memoria heap).

Esempio di codice:

#include <iostream> #include <stdexcept> void mayFail(bool fail) { if (fail) throw std::runtime_error("Errore di processo"); } int main() { try { mayFail(true); } catch (const std::exception& ex) { std::cout << "Eccezione catturata: " << ex.what() << std::endl; } }

Domanda trabocchetto

Cosa succede se un'eccezione viene lanciata da un distruttore durante la propagazione di un'altra eccezione nello stack?

Risposta: Avverrà un arresto anomalo del programma tramite la chiamata a std::terminate(), poiché durante la gestione di un'eccezione non è permesso generare una nuova eccezione (double exception), altrimenti si compromette lo stack unwinding.

Esempio:

struct CrashOnDestruct { ~CrashOnDestruct() noexcept(false) { throw std::runtime_error("Errore nel distruttore!"); } }; void func() { CrashOnDestruct obj; throw std::logic_error("Gestione errore"); } int main() { try { func(); } catch (...) { } } // Si concluderà tramite std::terminate()

Esempi di errori reali dovuti alla mancata conoscenza delle sottigliezze dell'argomento


Storia

All'interno di una grande applicazione finanziaria, le eccezioni venivano lanciate da un distruttore di una classe wrapper per un database. Una delle transazioni centrali, quando si verificava un errore, generava un'eccezione, e durante lo sforzo di unwind veniva chiamato il distruttore, che generava anche un errore. Tutta l'applicazione si arrestava anomamente, perdendo il lavoro degli utenti. I programmatori hanno dovuto sostituire in fretta il throw nei distruttori con la registrazione nei log e una gestione corretta.


Storia

Un microservizio gestiva le eccezioni usando: catch(std::exception). Uno dei thread lanciava eccezioni di tipi utente (non ereditati da std::exception). Questi errori non venivano catturati, si verificava un'interruzione imprevista della connessione e una perdita di memoria.


Storia

L'assenza del modificatore noexcept nei metodi di movimentazione dei contenitori ha portato il standard library a utilizzare una copia lenta invece di un'operazione di move rapida; un sostanziale fallimento nelle prestazioni è emerso solo nei test di carico.