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

C++try / catch / throw 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

C++Le eccezioni standard più importanti
/*
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

C++Creare la propria gerarchia di eccezioni
#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).

C++RAII — le risorse vengono sempre liberate
#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

C++noexcept — promessa che una funzione non lancia
#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;
}
🏋️ Esercizi
Pratica con eccezioni e RAII.
1
SafeArray con eccezioni
FacileEccezioni

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.

C++
#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;
}
2
Calculator con exception safety
MedioCustom exceptions

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.

C++
#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;
}