Newsgroup: it.comp.lang.c++
Titolo: uscire "graziosamente"

Ciao...una domanda base base, credo... se ho un programma che carica
vari file/alloca memoria piu' volte (ad esempio per costruire array di
oggetti con i dati contenuti nei file), come gestisco un fallimento in
una di queste operazioni? Ci sono due soluzioni ovvie, ma nessuna
delle due pare molto intrigante:
1)  me ne frego. Ho in giro degli statement del tipo
	if (error) return errorcode;
2) ho un programma fatto cosi'
	load(file1);	// wrapper
	if (errorcode)	// per semplicita' assumo controllo su
			// variabile globale...vabbe'...
		return (errorcode);
	...
	alloc(mem1);
	if (errorcode) {
		unload(file1);
		return (errorcode);
	}
	...
	load(file2);
	if (errorcode){
		unload(file1);
		unalloc(mem1);
		return(errorcode);
	}
	...
	alloc(mem2);
	if (errorcode){
		unload(file1);
		unalloc(mem1);
		unload(file2);
		return(errorcode);
	}
	...
mmm...sembra orrendo...naturalmente ci si puo' mettere nelle
condizioni di non scrivere cose del genere (
p.es
. non ci dovrebbe
essere motivo di tenere aperti tutti i file/allocata tutta la memoria
per tutto il corso del programma...intendo dire che non dovrebbe
esserci ragione di ritardare alla chiusura del main() il rilascio
delle risorse, anzi!
grazie in anticipo per le risposte...ciao
				Marcelo

 ha scritto nel messaggio
news:3a2bcbea...@netproxy.tst.cedborsa...



Costruttori,distruttori ed eccezioni, sono state create apposta :)
--
Ivan Molella
__LINUX__ the choice of the __GNU__ generation
icq# 97960484

 

Io alla fine uso:
        mem1=0
        mem2=0
        file1=0
        [...]
        if( errore ) goto esci;
        [...]
        if( errore2 ) goto esci;
esci:
        if( mem1 ) free( mem1 )
        if( mem1 ) free( mem1 )
        if( file1 ) close(file1)
Ora fucilatemi pure :(

meglio queste situazioni.
Per cortesia, quota in modo decente (sotto il testo e senza riportare
l'intero messaggio).
--
Andrea Laforgia (ICQ 87230455)
------------------------------ 
#exclude <windows.h>

        if( errore ) goto esci;
goto ?
GOTO!!!!!
Prendetelo! Prendetelo! ... ha usato un GOTO!!!!
Scherzi a parte questo sistema e' il migliore che conosco
quando si lavora in C. E' semplice e se viene seguito
sistematicamente mantiene un'ottima leggibilita' del codice.
In C++ pero' c'e' di meglio: creando una classe wrapper
e' possibile ottenere la garanzia che all'uscita della
funzione tutte le risorse allocate saranno liberate.
Per oggetti allocati dinamicamente esiste anche un
wrapper gia' pronto nella libreria standard (auto_ptr).
  bool Fun()
  {
    ...
    if (problem)
      return false;
    ...
    std::auto_ptr<MyClass> a(new MyClass);
    ...
    if (problem)
      return false;
    ...
    std::ofstream s(filename);
    ...
    if (problem)
      return false;
    ...
    return true;
  }
Il file s verra' chiuso se e stato aperto e l'istanza
puntata da a verra' deallocata (se e' stata allocata).
Questo indipendentemente da dove e perche' si esce
dalla funzione (return oppure exception).
Purtroppo non si puo' usare auto_ptr con gli array
(il problema e' un po' subdolo: auto_ptr non puo'
sapere se deve chiamare delete[] o delete) e quindi
e' decisamente meglio utilizzare std::vector invece
di array allocati dinamicamente (fra l'altro a
livello di prestazioni sono assolutamente equivalenti
e std::vector offre molte piu' funzionalita').
HTH
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 sistematicamente mantiene un'ottima leggibilita' del codice.
Mmh, nella mia esperienza gli "incartamenti" con le attivita' di cleanup
sono per la maggior parte dovuti all'abuso dei return multipli, che
rendono indispensabile la ripetizione di sequenze di istruzioni di
cleanup, via via incrementate in funzione della profondita' del punto di
uscita.
Spesso semplicemente strutturando il codice come si deve la collocazione
delle attivita' di cleanup e' immediata (e non richiede goto). Tra
l'altro in C non c'e' nemmeno il problema di evitare chiamate inutili a
costruttori/distruttori...
Ciao,
Nicola

delle attivita' di cleanup e' immediata (e non richiede goto).
Ragazzi, so che sto per proporre una schifezza, ma un codice come il
seguente come lo giudicate ?
result = true ;
for ( int i = 0 ; i < N_AZIONI ; i++ )
{
    if ( result )
       result = eseguiAzione( i ) ;
    else
       for ( j = 0 ; j < i ; j++ )
           chiudiAzione( j ) ;
}
A mio parere smembrerebbe troppo il corpo di un'unica funzione in
troppe sotto-azioni, pero' la sto provando e pare funzionare.


Preferisco la forma:
bool result = eseguiAzione(1);
if ( result )
{
  result = eseguiAzione(2);
  if ( result )
  {
    result = eseguiAzione(3);
    chiudiAzione(3);
  }
  chiudiAzione(2);
}
chiudiAzione(1);
A me risulta piu' chiaro cosa si vuole ottenere. Se ci sono azioni
particolari da compiere a fronte di errori, basta aggiungere gli
opportuni rami else. Nota che eseguo le chiudiAzione in ordine inverso
alle tue, ma secondo me e' piu' naturale (posto che chiudiAzione(n) sia
effettivamente associata a eseguiAzione(n) ).
Ciao,
Nicola

 wrapper gia' pronto nella libreria standard (auto_ptr).
Il tuo discorso e' giustissimo quando si ha a che fare con "oggetti" 
standard del tipo: puntatori a zone di memoria e file. Altrimenti la 
creazione di una classe wrapper va considerata caso per caso.
Comunque convengo che e' la soluzione migliore ( anche xche' cosi' si 
possono usare in maniera pulita le eccezzioni, che con il "mio" metodo non 
funzionano )

effettivamente associata a eseguiAzione(n) ).
La semantica non e` la stessa. Nel caso proposto
se per un certo ii si ha fallimento, si esegue la
chiudiAzione per j<i per i>=ii e i<N
(a meno di un mio errore di interpretazione).
Inoltre a lungo andare il tuo meccanismo soffre di problemi legati
alla manutenibilita`.
Ciao,
Paolo

troppe sotto-azioni, pero' la sto provando e pare funzionare.
Una possibilita` che sfrutta le eccezioni:
for (int i=0; i<N; ++i)
  try {
    if (!eseguiAzione(i)) throw std::pair<int, int>(i, N);
  }
  catch(const std::pair<int, int>& p) {
    for (int h=p.first; h<p.second; ++h)
      for(int j=0; j<h; ++j)
        chiudiAzione(j);
  }
Il contenuto del ramo catch potrebbe a questo punto essere incapsulato
in una apposita routine di gestione dell'errore, aumentando la
chiarezza del codice.
Ciao,
Paolo  
p.s.
   Ti faccio notare che se al passo i si verifica un errore,
    in tutti i restanti passi viene ripetuto il ciclo che esegue le
    chiudiAzione... Non so se era questo che volevi.
    Per inciso, tale comportamento non e` immediatamente evidente
    perche` (almeno per quanto mi riguarda) si e` portati a pensare
    che il fallimento di una azione comporti un aborto delle restanti
    (che quindi non andrebbero chiuse).

 Paolo
A proposito, ben tornato! (spero questo non significhi che ti hanno
licenziato :-)
Ciao,
Nicola

Senza contare che in C++ "moderno" si scriverebbe:
classeCheIncorporaAzione1 a1;
classeCheIncorporaAzione2 a2;
...
classeCheIncorporaAzionen an;
i costruttori lanciano un eccezione in caso di fallimento. Le azioni
successive non vengono eseguite e viene eseguito il cleanup (nei
relativi distruttori) solo per le azioni effettivamente eseguite.
Ciao,
Nicola

licenziato :-)
No, non mi hanno licenziato :-)... pero` ho veramente poco tempo
per cui non so se e quanto potro` seguire il NG anche perche`
dovrei rimettere mano al mio tutorial e farlo crescere per bene.
Inoltre sto cominciando a studiarmi java ed altre cosucce...
Cmq grazie per il "ben tornato", mi fa piacere vedere che
ci siete ancora tutti (tranne Alex Martelli, i cui sermoni
contribuivano notevolemente alla massa di cose da leggere!! :-)
Saluti,
Paolo

 contribuivano notevolemente alla massa di cose da leggere!! :-)
Se provi nostalgia, puoi sempre registrarti a comp.lang.python, dove
arringa sul calcolo delle probabilita' delle mani di bridge (e non e'
uno scherzo :-)
Ciao,
Nicola

uno scherzo :-)
E' vero. Ho visto parecchi suoi messaggi sui newsgroup di bridge
americani e pare che dia lezioni anche li' ! :)


uno scherzo :-)
Al momento le probabilita` non mi interessano!!!! :-o
Paolo