Gestione delle eccezioni in OOP
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 81 di 104.
La gestione delle eccezioni in C++ fornisce un modo strutturato per rilevare e gestire gli errori in fase di esecuzione. Nella programmazione orientata agli oggetti, le eccezioni si integrano naturalmente con le gerarchie di classi, permettendoti di creare tipi di errore significativi che forniscono informazioni sul problema che si è verificato.
Il meccanismo di base usa tre parole chiave: try, throw e catch. Il codice che potrebbe non riuscire va in un blocco try, gli errori vengono segnalati con throw e i blocchi catch gestiscono tipi specifici di eccezioni:
#include <iostream>
#include <stdexcept>
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount account(100.0);
try {
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Balance: " << account.getBalance() << "\n";
}La libreria standard C++ fornisce una gerarchia di classi di eccezioni che ha come radice std::exception. Tra le classi derivate più comuni ci sono std::runtime_error per i problemi di runtime e std::logic_error per gli errori di programmazione. Tutte le eccezioni standard forniscono un metodo what() che restituisce un messaggio di errore.
Quando viene lanciata un'eccezione, lo stack si svolge: vengono chiamati i distruttori di tutti gli oggetti locali mentre il programma cerca un blocco catch corrispondente. Questo garantisce una corretta pulizia anche quando si verificano errori, ed è per questo che RAII e la gestione delle eccezioni funzionano così bene insieme.
Sfida
FacileCostruiamo una calcolatrice con divisione sicura che dimostra la gestione delle eccezioni in un contesto orientato agli oggetti. Creerai una classe Calculator che esegue operazioni di divisione e genera eccezioni significative quando qualcosa va storto, poi gestirai queste eccezioni in modo appropriato nel programma principale.
Organizzerai il codice in tre file:
Calculator.h: Definisci la classeCalculator, che esegue operazioni matematiche con una corretta gestione degli errori.La classe
Calculatordovrebbe avere un metododivide(double numerator, double denominator)che restituisce il risultato della divisione. Tuttavia, se il denominatore è zero, dovrebbe generare unastd::runtime_errorcon il messaggioDivision by zero. Se il numeratore o il denominatore è negativo, dovrebbe generare unastd::invalid_argumentcon il messaggioNegative values not allowed.Includi anche un metodo
safeSqrt(double value)che restituisce la radice quadrata del valore. Se il valore è negativo, genera unastd::runtime_errorcon il messaggioCannot compute square root of negative number. Dovrai includere<cmath>per la funzionesqrt.Calculator.cpp: Implementa i metodi dichiarati nel file header. Assicurati di includere gli header necessari (<stdexcept>e<cmath>) e di controllare le condizioni prima di eseguire le operazioni.main.cpp: Leggi tre input (ciascuno su una riga separata):- Tipo di operazione:
divideoppuresqrt - Primo numero (double)
- Secondo numero (double): usato solo per la divisione, ignorato per sqrt
Crea un oggetto
Calculatored esegui l'operazione richiesta all'interno di un blocco try. Se l'operazione riesce, stampaResult: [value]. Se viene catturata un'eccezione, stampaError: [message], dove il messaggio proviene dal metodowhat()dell'eccezione.Dopo il blocco try-catch (indipendentemente dal fatto che si sia verificata un'eccezione), stampa
Calculation completeper dimostrare che il programma continua a essere eseguito dopo aver gestito le eccezioni.- Tipo di operazione:
Per esempio, con gli input divide, 10 e 2:
Result: 5
Calculation completeCon gli input divide, 10 e 0:
Error: Division by zero
Calculation completeCon gli input divide, -5 e 2:
Error: Negative values not allowed
Calculation completeCon gli input sqrt, 16 e 0:
Result: 4
Calculation completeCon gli input sqrt, -9 e 0:
Error: Cannot compute square root of negative number
Calculation completeNota come il programma gestisce gli errori in modo appropriato, catturando le eccezioni e visualizzando messaggi significativi, quindi continua l'esecuzione. Questo approccio mantiene pulita la gestione degli errori e permette alla logica principale di concentrarsi sul percorso senza errori.
Provalo tu
#include <iostream>
#include <string>
#include "Calculator.h"
using namespace std;
int main() {
// Leggi gli input
string operation;
double num1, num2;
cin >> operation;
cin >> num1;
cin >> num2;
// Crea un oggetto Calculator
Calculator calc;
// TODO: Usa un blocco try-catch per eseguire l'operazione
// - Se operation è "divide", chiama calc.divide(num1, num2)
// - Se operation è "sqrt", chiama calc.safeSqrt(num1)
// - Se l'operazione riesce, stampa "Result: " seguito dal risultato
// - Intercetta le eccezioni e stampa "Error: " seguito dal messaggio dell'eccezione (usa what())
// - Dopo il blocco try-catch, stampa sempre "Calculation complete"
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online