Newsgroup: it.comp.lang.c++
Titolo: Ancora sulle eccezioni

Considerate il seguente codice:
class Thrower {
	public:
  	class Err { };
    void ThrowError() { throw Err(); }
};
bool ReturnValue();
int main() {
	Thrower a,b;
	try {
		bool n=ReturnValue;
	  	if (n) 
			a.ThrowError(); 
		else 
			b.ThrowError();
	}
	catch(Thrower::Err) {
	  	// come faccio a sapere se l'eccezione è lanciata
    	// dall'oggetto a o dall'oggetto b?
	}
}
Supponendo di non potere modificare la definizione di Thrower come
faccio a sapere quale istanza della classe ha lanciato l'errore? Una
proposta potrebbe essere di spostare "bool n" al di fuori del blocco
try e di riesaminarla in catch() ma non mi sembra elegante; oppure non
c'è rimedio e la causa è un errore di design?
Anto
PS Questo non è un problema reale, solo una curiosità che mi è sorta
leggendo lo Stroustrup.

 
Invece quello che dici è corretto.
Lo scope del Try mette sullo stesso piano tutte le istruzioni come potenziali origini di eccezioni, l'unica discriminante è il tipo di eccezione.
Altrimenti devi fare come dici tu che non è inelegante bensì corretto.
Casomai un'idea inelegante potrebbe essere quella di usare il valore di n all'interno del blocco catch per cercare di indovinare l'origine dell'eccezione... perchè per esempio chi mi garantisce che l'eccezione non sia stata generata da ReturnValue() ?
HTH
_______________________________________
 Luca Minudel   Consulente Informatico
 
luca.m...@nline.it
 Conegliano (TV)
 WWW    
http://www.nline.it/consulting

 c'è rimedio e la causa è un errore di design?
Se non vuoi apportare modifiche a Thrower e non vuoi essere "inelegante" ti
basti l'eccezione anonima...
Ciao
Domenico Barra

 faccio a sapere quale istanza della classe ha lanciato l'errore?
bool n=ReturnValue;
 if (n)
 try {
        a.ThrowError();
} catch(Thrower::Err) { }
else
try {
         b.ThrowError();
} catch(Thrower::Err) { }
Ma è sbagliato a livello concettuale, non si cattura un eccezione su un
caso non eccezionale (ad esempio l'apertura di un file), ma si cattura
nell'eventualità che capiti qualcosa di "strano", per evitare che il
programma si chiuda quando in realtà si potrebbe recuperlo da una
posizione "sana". Supponi ad esempio di eseguire un aggiornamento ad un
database e succede qualcosa di "strano": si comunica il problema
all'utente e si riprende dall'uscita della procedura di aggiornamento.
Supponi invece che capiti un errore "normale", ad esempio l'utente ha
immesso un valore sbagliato, non lo gestisci con le eccezioni.
E' per questo che di solito si generano delle classi di eccezioni che
vengono lanciate (con una stringa da mostrare all'utente) e non ci si
interessa molto di quale oggetto ha lanciato l'eccezione.
Ciao FBF.

 c'è rimedio e la causa è un errore di design?
non puoi sapere chi ha lanciato l'eccezione. Se vuoi la mia opinione,
dovresti sempre cercare di scrivere dei blocchi try - catch che
riguardino una sola operazione (atomica), e di cui tu possa avere
tutte le informazioni nel caso di sollevamento di eccezione.
In parole povere, al momento del blocco catch tu dovresti sapere
esattamente cosa devi fare a seconda di quale eccezione  è stata
sollevata, e dubbi del genere che ti poni tu sono, IMHO, errori di
design.
Nel caso specifico, l'eccezioni secondo me sono usate male: se in ogni
caso pervieni al sollevamento di un'eccezione, significa che queste
sono la norma, non una 'eccezione'. Scusa il gioco di parole, ma
'eccezione' significa 'comportamento anomalo' e nel tuo caso uno dei
due rami dell'if dovrebbe essere il comportamento anomalo, l'altro il
comportamento corretto.

generata da ReturnValue() ?
Err è dichiarata nello scope di Thrower, quindi IMHO lanciarla da
ReturnValue() sarebbe un madornale errore di design; inoltre Thrower e
ReturnValue() sono a due livelli differenti di astrazione (anche se
qui forse non è tanto chiaro): Thrower è (in teoria) una classe di
libreria scritta da altri, ReturnValue() è una funzione scritta da me.
Però forse ci stiamo concentrando troppo sul caso (pur semplice)
specifico, non su quello generale.
Anto

la mia frase
Ops! 
Quello che volevo dire è che se vuoi distinguere l'origine sarebbe corretto
fare due blocchi Try, uno che contiene solo a.ThrowError(). e uno che
contiene solo b.ThrowError(). 
Parlando di ReturnValue() volevo solo fare un esempio per dire che se per
ipotesi ReturnValue() lanciasse una medesima eccezione, la variabile bool n
non ti sarebbe di aiuto comunque.
Quindi se vuoi individuare l'origine, la soluzione corretta è quella di cui
sopra.
Ciao
______________
 Luca Minudel
-- 
Posted from [62.110.75.135] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG

comportamento corretto.
Certo, hai ragione, ma l'esempio era ridotto ai minimi termini per
fare risaltare questo dubbio che mi era venuto, non lo farei mai in un
programma.
Anto

sopra.
OK, quindi il mio codice diventerebbe qualcosa del tipo:
if (n) then 
	try {
		a.ThrowError();
	}
	catch(Thrower::Err) { }
else
	try {
		b.ThrowError();
	}
	catch(Thrower::Err) { }
Credo che non avendo la possibiltà di modificare Thrower questa sia
l'unica soluzione plausibile, anche se rende vana una delle
prerogative delle eccezioni, cioè quella di separare in modo chiaro e
distinto il flusso normale del programma da quello eccezionale. Cosa
ne pensate?
Grazie
Anto

 "operazione atomica"?
dunque, prima di tutto premetto che quello che scriverò dopo non sarà
la vera definizione di 'atomico'. Il problema è che mentre scrivevo la
mail mi sono detto 'per adesso lascio atomico come promemoria, poi lo
cambio' e mi sono scordato di cambiarlo. Comunque, intendevo dire che
il blocco di istruzioni racchiuso nel try-catch dovrebbe riguardare
un'operazione compiuta su una o più oggetti che riguardano una singola
azione astratta. Del tipo:
try
{
    fornello.accendi();
    fornello.inserisci( pollo());
    fornello.aspetta_cottura();
}
catch ( const esplosione_fornello &exc)
{
    if ( exc.non_grave())
        usa_estintore();
    else
        muori();
}
come vedi tutte le azioni riguardano il fatto di cucinarsi un pollo
arrosto: un'operazione ad alto livello. Se pervieni al blocco catch,
significa che la cottura del pollo non è andata a buon fine perchè è
esploso il fornello. Per intenderci, nel blocco try-catch non andrebbe
messo il codice per servire il pollo a tavola, che è un'altra
operazione astratta. Osserva poi che l'eccezione è quantomeno rara, se
vi fosse un'alta probabilità che il forno esploda, allora non dovrebbe
essere un'eccezione ma magari un codice di ritorno particolare, etc.

OK, sei stato chiarissimo; ora però sono curioso: cosa significa
"atomico" (a parte la bomba :-)?
Anto

Letteralmente "Non tagliabile", ovvero indivisibile, non scomponibile in
elementi piu' piccoli. Quando si parla di istruzione atomica si intende
un'istruzione eseguita in modo continuativo dall'inizio alla fine, senza
interruzioni (e dunque senza interferenze o intromissioni).
Ciao,
Nicola