Gestione delle Eccezioni
try/catch/throw, gerarchia degli errori, RAII e exception safety. Come scrivere codice robusto in C++.
Cos'è un'eccezione?
Un'eccezione è un meccanismo per gestire condizioni di errore in modo strutturato. Invece di restituire codici di errore (fragili e facili da ignorare), il C++ permette di lanciare (throw) un oggetto di errore che viene catturato (catch) dove ha senso gestirlo.
try
Blocco di codice che potrebbe lanciare un'eccezione.
throw
Lancia un'eccezione. Il controllo salta al catch più vicino.
catch
Cattura e gestisce l'eccezione.
RAII
Le risorse vengono liberate automaticamente allo stack unwinding.
Sintassi Base
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
double divisione(double a, double b) {
if (b == 0.0)
throw runtime_error("Divisione per zero!");
return a / b;
}
int accedi_array(int arr[], int n, int idx) {
if (idx < 0 || idx >= n)
throw out_of_range("Indice " + to_string(idx) +
" fuori range [0," + to_string(n-1) + "]");
return arr[idx];
}
int main() {
// Esempio base
try {
double r = divisione(10, 0);
cout << r << "\n"; // Non raggiunto
} catch (const runtime_error &e) {
cout << "Errore: " << e.what() << "\n";
}
// Più catch
try {
int arr[] = {1, 2, 3};
cout << accedi_array(arr, 3, 5) << "\n";
} catch (const out_of_range &e) {
cout << "Fuori range: " << e.what() << "\n";
} catch (const exception &e) {
cout << "Errore generico: " << e.what() << "\n";
} catch (...) {
cout << "Errore sconosciuto!\n";
}
// Il codice continua dopo il catch
cout << "Il programma continua...\n";
return 0;
}
Gerarchia delle Eccezioni Standard
/*
std::exception
├── std::runtime_error // Errori a runtime
│ ├── std::overflow_error
│ ├── std::underflow_error
│ └── std::range_error
├── std::logic_error // Errori di logica del programma
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
├── std::bad_alloc // malloc/new fallisce
├── std::bad_cast // dynamic_cast fallisce
└── std::bad_typeid
*/
#include <iostream>
#include <stdexcept>
#include <new>
using namespace std;
void test_eccezioni_standard() {
// out_of_range — string::at(), vector::at()
try {
string s = "ciao";
char c = s.at(100); // Lancia out_of_range
} catch (const out_of_range &e) {
cout << "out_of_range: " << e.what() << "\n";
}
// bad_alloc — new non riesce ad allocare
try {
int *p = new int[99999999999LL]; // Probabilmente fallisce
delete[] p;
} catch (const bad_alloc &e) {
cout << "bad_alloc: " << e.what() << "\n";
}
// invalid_argument
try {
throw invalid_argument("Argomento non valido");
} catch (const invalid_argument &e) {
cout << "invalid_argument: " << e.what() << "\n";
}
}
int main() {
test_eccezioni_standard();
return 0;
}
Eccezioni Personalizzate
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
// Eccezione base per l'applicazione
class AppException : public runtime_error {
int codice;
public:
AppException(int cod, const string &msg)
: runtime_error(msg), codice(cod) {}
int getCodice() const { return codice; }
};
// Eccezioni specifiche
class DatabaseException : public AppException {
public:
DatabaseException(const string &msg)
: AppException(1001, "DB Error: " + msg) {}
};
class ValidationException : public AppException {
string campo;
public:
ValidationException(const string &c, const string &msg)
: AppException(400, "Validation Error on '" + c + "': " + msg), campo(c) {}
const string &getCampo() const { return campo; }
};
class NetworkException : public AppException {
int http_code;
public:
NetworkException(int code, const string &msg)
: AppException(code, "Network " + to_string(code) + ": " + msg),
http_code(code) {}
int getHttpCode() const { return http_code; }
};
// Funzione che potrebbe lanciare vari errori
void registra_utente(const string &email, const string &pwd) {
if (email.find('@') == string::npos)
throw ValidationException("email", "non contiene @");
if (pwd.size() < 8)
throw ValidationException("password", "troppo corta (min 8 caratteri)");
// Simulazione errore DB
if (email == "admin@test.com")
throw DatabaseException("utente già esistente");
cout << "Utente " << email << " registrato con successo!\n";
}
int main() {
vector<pair<string,string>> tentativi = {
{"pippo", "password123"}, // Email invalida
{"a@b.com", "abc"}, // Password corta
{"admin@test.com", "pass1234"}, // DB error
{"mario@email.com", "sicura123"} // OK
};
for (auto &[email, pwd] : tentativi) {
try {
registra_utente(email, pwd);
} catch (const ValidationException &e) {
cout << "[" << e.getCodice() << "] Campo '" << e.getCampo()
<< "': " << e.what() << "\n";
} catch (const DatabaseException &e) {
cout << "[" << e.getCodice() << "] DB: " << e.what() << "\n";
} catch (const AppException &e) {
cout << "[" << e.getCodice() << "] App: " << e.what() << "\n";
}
}
return 0;
}
RAII e Stack Unwinding
RAII (Resource Acquisition Is Initialization): acquisisci le risorse nel costruttore, liberale nel distruttore. Quando viene lanciata un'eccezione, tutti i distruttori degli oggetti locali vengono chiamati automaticamente (stack unwinding).
#include <iostream>
#include <stdexcept>
#include <memory>
#include <fstream>
using namespace std;
// SENZA RAII — la memoria si perde se viene lanciata un'eccezione!
void senza_raii() {
int *dati = new int[1000];
// ... elaborazione ...
throw runtime_error("errore!"); // dati NON viene liberato! MEMORY LEAK
delete[] dati; // Mai raggiunto
}
// CON RAII (smart pointer) — il distruttore chiama delete automaticamente
void con_raii() {
auto dati = make_unique<int[]>(1000); // RAII
// ... elaborazione ...
throw runtime_error("errore!");
// dati.~unique_ptr() viene chiamato automaticamente — NESSUN LEAK
}
// RAII per file
class FileRaii {
FILE *fp;
public:
FileRaii(const char *path, const char *mode) {
fp = fopen(path, mode);
if (!fp) throw runtime_error(string("Impossibile aprire: ") + path);
cout << "File aperto\n";
}
~FileRaii() { if (fp) { fclose(fp); cout << "File chiuso\n"; } }
FILE *get() { return fp; }
};
// RAII per mutex lock
class LockGuard {
bool *mutex;
public:
LockGuard(bool *m) : mutex(m) { *mutex = true; cout << "Mutex acquisito\n"; }
~LockGuard() { *mutex = false; cout << "Mutex rilasciato\n"; }
};
int main() {
// RAII garantisce la chiusura del file anche con eccezioni
try {
FileRaii f("/tmp/test_raii.txt", "w");
fprintf(f.get(), "test\n");
throw runtime_error("Errore durante la scrittura");
// Il file viene chiuso comunque nel distruttore!
} catch (const exception &e) {
cout << "Eccezione: " << e.what() << "\n";
}
// Prova con noexcept
auto cleanup = [](int *p) { delete[] p; cout << "Cleanup!\n"; };
{
unique_ptr<int[], decltype(cleanup)> p(new int[10], cleanup);
} // cleanup chiamato qui
return 0;
}
noexcept
#include <iostream>
#include <stdexcept>
using namespace std;
// noexcept: dichiara che la funzione non lancia mai
// Se lancia, il programma chiama std::terminate()
int somma(int a, int b) noexcept { return a + b; }
// noexcept condizionale
template<typename T>
void scambia(T &a, T &b) noexcept(noexcept(T(move(a)))) {
T tmp = move(a); a = move(b); b = move(tmp);
}
// Verifica se una funzione è noexcept
static_assert(noexcept(somma(1, 2))); // Compila solo se somma è noexcept
// Perché noexcept è importante?
// 1. Performance: il compilatore può ottimizzare meglio
// 2. Correttezza: documenta le garanzie della funzione
// 3. Move semantics: i move constructor devono essere noexcept
// per essere usati in std::vector::push_back
class Efficiente {
int *dati;
int n;
public:
Efficiente(int size) : dati(new int[size]), n(size) {}
~Efficiente() { delete[] dati; }
// Il move constructor è noexcept: std::vector lo userà durante il resize
Efficiente(Efficiente &&other) noexcept
: dati(other.dati), n(other.n) {
other.dati = nullptr; other.n = 0;
}
};
int main() {
cout << "somma noexcept: " << boolalpha << noexcept(somma(1,2)) << "\n";
int a=10, b=20;
scambia(a, b);
cout << "Dopo swap: a=" << a << " b=" << b << "\n";
return 0;
}
Crea una classe SafeArray che wrapperizza un array C++ con accesso sicuro. L'operatore [] lancia out_of_range per indici invalidi. Aggiungi anche front() e back() che lanciano se l'array è vuoto.
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class SafeArray {
int *dati;
int n;
public:
SafeArray(int size) : dati(new int[size]()), n(size) {}
~SafeArray() { delete[] dati; }
int &operator[](int i) {
if(i<0||i>=n) throw out_of_range("Indice "+to_string(i)+" non valido (size="+to_string(n)+")");
return dati[i];
}
const int &operator[](int i) const {
if(i<0||i>=n) throw out_of_range("Indice "+to_string(i)+" non valido");
return dati[i];
}
int &front() { if(!n) throw runtime_error("Array vuoto"); return dati[0]; }
int &back() { if(!n) throw runtime_error("Array vuoto"); return dati[n-1]; }
int size() const { return n; }
};
int main() {
SafeArray a(5);
for(int i=0;i<5;i++) a[i]=i*10;
try { cout << a[10] << "\n"; }
catch(const out_of_range &e){ cout << "Errore: " << e.what() << "\n"; }
try { cout << a[-1] << "\n"; }
catch(const out_of_range &e){ cout << "Errore: " << e.what() << "\n"; }
cout << "front=" << a.front() << " back=" << a.back() << "\n";
SafeArray vuoto(0);
try { vuoto.front(); }
catch(const runtime_error &e){ cout << e.what() << "\n"; }
return 0;
}Implementa una calcolatrice in notazione RPN (postfissa) che usa eccezioni per: divisione per zero, stack underflow (troppi pochi operandi), operatori sconosciuti. La calcolatrice deve essere exception-safe: se un'operazione fallisce lo stack rimane consistente.
#include <iostream>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <cctype>
using namespace std;
class CalcolatriceException : public runtime_error {
public:
CalcolatriceException(const string &m) : runtime_error(m) {}
};
double calcola_rpn(const string &expr) {
stack<double> s;
istringstream iss(expr);
string token;
while (iss >> token) {
bool e_numero = !token.empty() && (isdigit(token[0]) || (token[0]=='-' && token.size()>1));
if (e_numero) {
s.push(stod(token));
} else {
if (s.size() < 2)
throw CalcolatriceException("Stack underflow: operatore '" + token + "' richiede 2 operandi");
double b = s.top(); s.pop();
double a = s.top(); s.pop();
if (token=="+") s.push(a+b);
else if (token=="-") s.push(a-b);
else if (token=="*") s.push(a*b);
else if (token=="/") {
if (b==0) throw CalcolatriceException("Divisione per zero");
s.push(a/b);
} else {
s.push(a); s.push(b); // Rimetti per exception safety
throw CalcolatriceException("Operatore sconosciuto: " + token);
}
}
}
if (s.size() != 1)
throw CalcolatriceException("Espressione non valida (stack size=" + to_string(s.size()) + ")");
return s.top();
}
int main() {
vector<string> test = {
"3 4 +", // 7
"5 1 2 + 4 * + 3 -", // 14
"10 0 /", // Div per zero
"3 +", // Stack underflow
"3 4 @" // Operatore sconosciuto
};
for (const auto &expr : test) {
try {
cout << "'" << expr << "' = " << calcola_rpn(expr) << "\n";
} catch (const CalcolatriceException &e) {
cout << "Errore: " << e.what() << "\n";
}
}
return 0;
}