ProgrammazioneProgrammatore di sistema C++

Parlaci dei meccanismi di lavoro delle eccezioni in C++. Come gestire correttamente gli errori e a cosa serve noexcept?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della questione:

Il meccanismo di gestione delle eccezioni è stato introdotto in C++ per garantire una gestione degli errori più affidabile e strutturata, a differenza dei codici di ritorno. Nel corso degli anni, la sintassi si è ampliata, sono stati introdotti i qualificatori noexcept per controllare la cancellazione delle eccezioni dalle funzioni.

Problema:

Un'errata gestione delle eccezioni porta spesso a perdite di memoria, comportamenti indefiniti o crash delle applicazioni. Se non si considerano le eccezioni sollevate dai costruttori, distruttori o durante la gestione delle risorse, si verifica un grave problema con lo stato del programma.

Soluzione:

La sintassi di base è l'uso di try-catch. Le eccezioni possono essere di qualsiasi tipo, ma si consiglia di ereditare le eccezioni personalizzate da std::exception. La parola chiave noexcept (C++11+) indica che una funzione non deve sollevare eccezioni; il lancio di un'eccezione da una funzione noexcept causa std::terminate().

Esempio di codice:

#include <iostream> #include <stdexcept> void func() noexcept(false) { throw std::runtime_error("Errore!"); } int main() { try { func(); } catch(const std::exception& ex) { std::cout << ex.what(); } }

Caratteristiche chiave:

  • Le eccezioni forniscono un unwind automatico (svuotamento dello stack)
  • noexcept consente al compilatore di ottimizzare se una funzione non lancia eccezioni
  • Solo std::exception e le sue derivate garantiscono la presenza del metodo what()

Domande trabocchetto.

È lecito lanciare eccezioni nel distruttore?

No, lanciare un'eccezione da un distruttore durante lo svuotamento dello stack causa std::terminate(). Le eccezioni nel distruttore devono essere catturate all'interno dello stesso distruttore.

Cosa succede se si lancia un'eccezione da una funzione noexcept?

Viene chiamato std::terminate, il programma termina in modo anomalo. noexcept è un contratto rigoroso.

Si possono catturare eccezioni per valore? Perché è pericoloso?

Si possono catturare per valore, ma l'oggetto viene copiato (object slicing). È consigliato utilizzare const &.

Esempio di codice:

try { throw std::out_of_range("errore"); } catch (const std::exception& e) { /* ... */ }

Errori tipici e anti-pattern

  • Utilizzo di throw nei distruttori
  • Cattura delle eccezioni catch(...) senza logging
  • Mancata indicazione di noexcept dove possibile

Esempio dalla vita reale

Caso negativo

Il codice lanciava eccezioni nei distruttori delle risorse; in caso di terminazione anomala, lo stack si svuotava in modo errato, causando perdite di risorse.

Vantaggi:

  • Possibile rilevamento rapido degli errori

Svantaggi:

  • I programmi spesso terminavano con un errore di terminate

Caso positivo

I metodi critici erano contrassegnati come noexcept, i distruttori gestivano tutte le eccezioni all'interno di sé. Si utilizzavano catch per riferimento, tutti gli errori venivano registrati.

Vantaggi:

  • Affidabilità, assenza di perdite
  • Comportamento estremamente prevedibile

Svantaggi:

  • Maggiore rigore richiesto allo sviluppatore nella scrittura di codice corretto e "pulito"