Newsgroup: it.comp.lang.c++
Titolo: Uscire da loop nidificati

Quando siete all'interno di N loop nidificati quale sistema utilizzate 
o ritenete piu' elegante?

 ritenete piu' elegante?
goto

 elegante?
lo so che è orrendo ma in questo senso uso due
strategie orrende. Se la natura dei cicli lo
richiede tratto il C++ come il pascal, controllando
le condizioni di fermata a partire dai loop interni.
In altri casi, quando mi sembra che la logica del
ciclo lo richieda, uso goto, essenzialmente per
evitare troppi "return". Preferisco mettere
un'etichetta finale prima di "return" e dirigo
sempre su quella. So che goto è sconsigliato ma
preferisco così a una pletora di "return". Ma non
faccio testo visto che non sono programmatore :D
Ovviamente se la "logica" del programma lo vuole ci
sta pure "break" e ci sono le eccezioni...
-- 
"La teoria è sapere come funzionano le cose e
non sapere come farle funzionare; la pratica è
sapere come far funzionare la cose senza sapere come
funzionano; il nostro scopo è unire la pratica
alla teoria per riuscire a non far funzionare
le cose senza capire come abbiamo fatto"


Le strategie sono molte, ma i goto sono da usare con mooolta
attenzione, perché si rischia che alcune risorse non vengano liberate
correttamente -- per esempio chiudere socket aperti con open () in C,
etc., o di fare pastrocchi. In genere preferisco:
a) se i cicli annidati sono 2-3, usare una variabile booleana
sentinella nelle condizioni del ciclo
b) se sono di più, mi chiedo se non sia un mio errore di
programmazione, se posso spezzare in più funzioni quello che ho
scritto, se l'uso o implementazione di iteratori o funtori possa
aiutarmi
c) lancio un eccezione controllata, che eventualmente ha il pregio di
permettermi anche di ritornare dei valori dall'interno del ciclo più
innestato
d) se tutto il resto è troppo una rottura da usare, uso goto (tipo:
l'avrò fatto due volte nella mia vita).
Ciao,
Matteo

 attenzione
Io ho usato anche altri due metodi forse ancora piu' orrendi.
1) Se non mi interessa sapere come si e' concluso il ciclo imposto 
tutte le variabili / contatore sul valore finale.
2) Metto un break sul loop piu' interno e poi sui vari loop annidati 
controllo se l'indice non e' arrivato al fondo (quindi sono uscito dal 
break) continuo con i break. etc.
Goto davvero non l'ho mai usato, ma quella delle eccezioni mi sembra 
molto elegante.

 molto elegante.
E' buono, l'importante è non abusarne. Consiglio il libro di Herb
Sutter, "Exceptional C++", che contiene anche parecchi esercizi sulle
eccezioni.
Ciao,
Matteo

 
Guarda non ho molto tempo per i dettagli, ma la soluzione + elegante (a 
parte quella di evitare se possibile loop troppo profondamente 
nidificati) ï¿œ il goto.
Viceversa l'utilizzo delle eccezioni in questo caso ï¿œ profondamente 
sbagliato sia da un punto di vista concettuale, sia da un punto di vista 
prestazionale e non ha NESSUN vantaggio rispetto al goto.
Cerca in rete e vedrai che tutti gli autori sono d'accordo nel dire che 
le eccezioni non vanno MAI usate per pilotare il flusso "normale" del 
programma. MAI.
Per pilotare il flusso si usano if, switch, while e compagnia bella e in 
casi rari e ben precisi il goto.
Uno dei motivi per cui il goto esiste (forse il + importante) ï¿œ proprio 
per uscire da loop nidificati in modo semplice e pulito senza 
appesantire il codice con inutili "orpelli" della programmazione 
strutturata che (IN QUESTO CASO) servono solo per generare un codice 
meno leggibile e lineare (vedi ad esempio booleani da testare in tutti i 
rami, sentinelle o altre schifezze analoghe)
Tutto IMHO.
Beppe

 goto.
Grazie mille Beppe, mi hai decisamente convinto a provare ad usare i 
goto.

 Guarda non ho molto tempo per i dettagli...
Se googli un po ne trovi a bizzeffe di guidelines al proposito...
Ad esempio ecco cosa dice Eckel (Thinking in C++)
Using and misusing goto
The goto keyword is supported in C++, since it exists in C. Using goto 
is often dismissed as poor programming style, and most of the time it 
is. Anytime you use goto, look at your code and see if there’s another 
way to do it. On rare occasions, you may discover goto can solve a 
problem that can’t be solved otherwise, but still, consider it 
carefully. Here’s an example that might make a plausible candidate:
//: C03:gotoKeyword.cpp
// The infamous goto is supported in C++
#include <iostream>
using namespace std;
int main() {
   long val = 0;
   for(int i = 1; i < 1000; i++) {
     for(int j = 1; j < 100; j += 10) {
       val = i * j;
       if(val > 47000)
         goto bottom;
         // Break would only go to the outer 'for'
     }
   }
   bottom: // A label
   cout << val << endl;
} ///:~
The alternative would be to set a Boolean that is tested in the outer 
for loop, and then do a break from the inner for loop. However, if you 
have several levels of for or while this could get awkward.
E ancora (sempre Eckel Thinking In C++ Volume 2: Practical Programming)
When to avoid exceptions
...
Not for flow–of–control
An exception looks somewhat like an alternate return mechanism and 
somewhat like a switch statement, so you might be tempted to use an 
exception instead of these ordinary language mechanisms. This is a bad 
idea, partly because the exception-handling system is significantly less 
efficient than normal program execution. Exceptions are a rare event, so 
the normal program shouldn’t pay for them. Also, exceptions from 
anything other than error conditions are quite confusing to the user of 
your class or function.
HTH
Beppe

Guglielmo Calligaro ha scritto:

Leggi anche l'altra mia risposta (la stavo copiaincollando mentre 
postavi tu ) cosï¿œ ti convinci ancora meglio :)

> Se googli un po ne trovi a bizzeffe di guidelines al proposito...
Si e' vero. Ho fatto una veloce ricerca nei miei sorgenti e ho circa 
7817 break! Ne ho rivisitati alcuni e davvero la leggibilita' del 
codice ci guadagna tantissimo: invece di perderti a cercare un break 
dentro ad un if (anch'esso magari nidificato) dove cavolo ti porta, un 
bel goto e non ti puoi sbagliare.
Intanto sono in procinto di riportare di riscrivere tutto questo 
vecchio progetto e quindi sapro' come comportarmi!

 correttamente -- per esempio chiudere socket aperti con open () in C,
Questo non � assolutamente vero.
il goto salta altrove esattamente come ogni altro modo per saltare 
altrove... con esattamente gli stessi rischi di lasciare risorse allocate.
Per evitare di lasciare risorse allocate, bisogna usare RAII.
I goto non sono da usare quando esiste un costrutto strutturato 
*EQUIVALENTE*
Ad esempio questo � da evitare e da sostituire ad esempio con un for:
int i = 0;
label:
//fai qualcosa
++i;
if (i<10) goto label;
Beppe

 
bool bLavoro = true;
int n1 = 10000, n2 = 1000, n3 = 100;
for (int i1 = 0; (bLavoro && i1 < n1); i1++)
{
   for (int i2 = 0; (bLavoro && i2 < n2); i2++)
   {
     for (int i3 = 0; (bLavoro && i3 < n3); i3++)
     {
	if (vincoSuperEnalotto())
	{
     	  bLavoro = false;
	  break;
	}	
     }
   }
}
if (bLavoro)
	cout << "Sei il solito impiegato, torna a lavorare!\n";
else
	cout << "Apri una ditta di prodotti software ed assumi i tuoi capi...\n";
Maurus

     {
si ma ... con 3, 4, 5 o più cicli annidati, quante volte 
viene valutata la condizione di controllo ?
Se le operazioni svolte nel ciclo sono di per sé rapide (e 
non vedo perché no, ad un tale livello di annidamento), non 
ti pesa aggiungere un'istruzione extra evitabile, solo per 
eseguire ridondantemente un controllo ?
Intendo non che sia sbagliato, assolutamente.
Ma in cicli profondamente annidati, l'efficienza dovrebbe 
cominciare ad essere un aspetto da considerare, e aggiungere 
un costo fisso costante (il test ulteriore) non mi sembra la 
scelta migliore, elegante o meno .... Personalmente non mi 
piace nemmeno troppo perché fa leggere più codice, ma questo 
è irrilevante
ciao
Soviet


 }
...
Non per fare polemica, ma questo ï¿œ proprio il genere di "arzigogoli" che 
rende il codice meno efficiente (forse ï¿œ trascurabile) ma sicuramente 
meno leggibile di un semplice goto.
Senza contare il fatto che se ci fossero state istruzioni da eseguire 
dopo i for interni, l'"arzigogolo" avrebbe dovuto essere ancora piï¿œ 
complesso.
Riporto le parole di eckel che erano riferite ad un esempio esattamente 
come questo: "The alternative would be to set a Boolean that is tested 
in the outer for loop, and then do a break from the inner for loop. 
However, if you have several levels of for or while this could get awkward."
IMHO L'alternativa col goto ï¿œ decisamente piï¿œ leggibile (e funziona 
anche se ci fossero state altre istruzioni dopo i for):
bool bLavoro = true;
int n1 = 10000, n2 = 1000, n3 = 100;
for (int i1 = 0; i1 < n1; i1++)
{
   for (int i2 = 0; i2 < n2; i2++)
   {
     for (int i3 = 0; i3 < n3; i3++)
     {
       if (vincoSuperEnalotto())
       {
           bLavoro = false;
	  goto fine;
       }
       //eventuali altre istruzioni
     }
     //eventuali altre istruzioni
   }
   //eventuali altre istruzioni
}
fine:
//come sopra

Sarò testone, ma continuo a pensare che i goto siano nel 99% dei casi
sorgente di confusione.
Io non sono così integralista come Dijkstra (
http://www.cs.utexas.edu/
users/EWD/ewd02xx/EWD215.PDF), tuttavia penso che l'uso di goto debba
essere un'eccezione, non una regola.
Matteo

 sorgente di confusione.
Esatto. Questo � proprio quel 1% in cui ci vuole...
E.

 
NO!
La sorgente di confusione sono degli inutili booleani con nomi 
"fantasiosi" che gi� da quello si intuisce che � na porcata.
Con gli inutili test che ci stanno a corredo.
Tutto questo rende il programma:
1 - pi� lento
2 - pi� difficile da scrivere
3 - pi� difficile da leggere
4 - pi� difficile da mantenere
5 - pi� prono agli errori
Sono TUTTI svantaggi.
L'unico "svantaggio" del goto in questo caso � la presunta "avversione" 
per la parola "goto". Stiamo scherzando?
Per non parlare dell'utilizzo delle eccezioni in questo caso: oltre a 
tutti gli svantaggi elencati sopra, � anche contrario a qualsiasi 
principio. Viola pesantemente il concetto stesso di eccezione!
Per quanto riguarda il parere di Dijkstra: ROTFL. Un articolo battuto a 
macchina del 1968 che cita un "pre-ALGOL meeting" del 1959!
Erano altri tempi, il goto veniva usato per fare altre cose! Su queste 
sicuramente ha ragione. Non l'ho letto tutto ma non mi sembra che parli 
dei loop nidificati in c++ (anche perch� il c++ non esisteva).

 programma.
In C++ questa e' prassi e il consiglio. Non e' necessariamente una
verita' assoluta. Ad ora normalmente le motivazioni addotte sono
"maggiore chiarezza del codice" (che in generale non mi sembra) *ed*
efficienza.
Di fatto l'efficienza e' critica quando, per definizione, l'efficienza
e' crititca. Quando non lo e', bisogna vedere cosa sia piu' leggibile.
In effetti ci mordiamo la coda: usare le eccezioni per gestire il flusso
in C++ non e' leggibile perche' i programmatori C++ non si aspettano
tale uso delle eccezioni. 
-- 
-riko

 In C++ questa e' prassi e il consiglio. 
...
Quello che hai scritto ï¿œ tutto vero e sensato.
Perï¿œ ammetterai che in questo caso ï¿œ un utilizzo alquanto improprio...
Esempio del cacchio ma per capire cosa voglio dire:
devo schiacciare una formica.
il goto equivale a schiacciarla semplicemente con un dito.
l'eccezione equivale a prendere un carro armato e usarlo non per 
schiacciarla e neanche sparando una cannonata (che al limite sarebbe 
solo un eccessivo spreco di risorse), ma premendo con la punta del 
cannone del carro armato tra la formica e il muro (un uso "improprio" e 
che rischia di fare altri danni)
:)

 il goto equivale a schiacciarla semplicemente con un dito.
Assolutamente d'accordo. 
-- 
-riko

 eccezioni e' una forma "disciplinata" di goto. 
...
Guarda, ci ho pensato un po su e questa notte mi � venuta 
l'illuminazione! Usare le eccezioni per modificare il flusso di 
esecuzione ha un GRAVISSIMO effetto collaterale che rende l'approccio 
ancora pi� sbagliato: incide sulle eccezioni stesse!
Se qualsiasi altra funzione che viene chiamata in qualsiasi parte dei 
miei loop nidificati generasse un'eccezione dello stesso tipo che io uso 
per simulare il goto, ho distrutto la logica che stava alla base di 
quella eccezione!
Posso ovviare al problema lanciando un'eccezione di un tipo definito 
localmente in modo da essere sicuro che nessun altra parte del codice 
lanci eccezioni dello stesso tipo... ma stiamo entrando nel campo degli 
arzigogoli quando un semplice goto risolveva egregiamente!
Mi spiego meglio con un esempio. Supponiamo di voler usare l'approccio 
"eccezioni" per uscire da un loop nidificato:
try
{
  for(y=0; y<5000; ++y)
  {
   //fai qualcosa 1
   for(x=0;x<3000; ++x)
   {
    //fai qualcosa 2
    for(j=0; j<30; ++j)
    {
      //fai qualcosa 3
      for(i=0; i<30; ++i)
      {
       //fai qualcosa 4
       if(devouscire)
        throw 1;
       //fai qualcosa 5
      }
      //fai qualcosa 6
    }
    //fai qualcosa 7
   }
   //fai qualcosa 8
  }
}
catch(int)
{
}
se qualsiasi istruzione in //fai qualcosa xxx lancia eccezioni di tipo 
int, sono fregato!
Spero di essermni spiegato.
Beppe

GM ha scritto:

Esiste un'altra tecnica che quando applicabile ï¿œ sicuramente la piï¿œ 
pulita di tutte (ed ï¿œ quella che di solito tutti utilizziamo senza 
neanche accorgercene).
Consiste nell'isolare i loop nidificati all'interno di una funzione e 
usare return per uscire. E' anche potente perchï¿œ posso usare il valore 
di ritorno per distinguere i vari casi.
Quando ho risposto goto, stavo implicitamente pensando che per qualche 
motivo questa possibilitï¿œ non fosse applicabile (ad esempio se c'ï¿œ un 
accoppiamento molto forte e stretto tra il codice che sta nei loop e 
quello che sta fuori)

 accorgercene).
Anche questa non e' male, sicuramente e' ancora piu' facile da leggere.
Io poi tendo a proliferare funzioni perche' mi sembrano un ottimo modo 
per migliorare la leggibilita' e concentrare l'attenzione sempre solo 
sulle parti importanti.
Pero' sicuramente e' piu' pratico e prestante mettere goto label e via.
Invece devi dichiarare una o n funzioni, analizzare quali parametri 
devono ricevere, cosa devono restituire, controllare le prestazioni  e 
tutto il resto ... decisamente piu' complicato.
Secondo me va valutata attentamente la complessita' dell'algoritmo per 
questo motivo vedremi bene il goto per le cose piu' semplici e le 
funzioni nelle situazioni piu' delicate.

 motivo questa possibilità non fosse applicabile (ad esempio se c'è un 


 miriadi di volte,
[...] si, ma nella funzione metti l'intero blocco di loop, che quindi
viene chiamata solo una volta. L'overhead della chiamata della
funzione
dovrebbe essere poco significativo rispetto al carico di lavoro dei
loop
annidati.
d.

 annidati.
Esatto.
Provo a buttare un esempio in "pseudocodice".
Supponiamo di voler cercare un elemento all'interno di una "griglia" 
bidimensionale. Se lo troviamo vogliamo fare qualcosa (esempio del 
cacchio ma significativo per capire l'idea)
col goto:
trovato=false;
for(y...)
{
  for(x...)
  {
    if(...) {trovato=true; goto fine;}
  }
}
fine:
if(trovato)
  FaiQualcosaConLElemento(x, y);
con la funzione:
bool Trova(int& x, int& y)
{
  for(y...)
  {
   for(x...)
   {
     if(...) return true;
   }
  }
  return false;
}
int x, y;
if(Trova(x, y))
  FaiQualcosaConLElemento(x, y);

    }
io comunque in dieci anni di onorata carriera di return dentro
un ciclo for ne avro' messi due:
mi da un fastidio tipo quello delle mogli per l'asse del cesso
lasciato alzato... :~)

 un ciclo for ne avro' messi due:
Quindi tu come la risolveresti questa situazione?

Dario Ballabio ha scritto:

OHH RAGAZZI ADESSO POI BASTA :) :) :)
Cacchio stiamo cercando un modo per uscire da loop nidificati.
Sar� lapalissiano ma comunque usciamo ... stiamo uscendo!
Lo scopo di quel return � proprio esattamente quello di eseguire 
l'uscita brutale con:
- un costo irrisorio
- un'ottima pulizia (da questo punto di vista meglio addirittura del 
goto che a sua volta considero meglio dei 
bool/test/sentinelle/arzigogoli che a sua volta � meglio delle eccezioni)
- ha l'ulteriore vantaggio di poter portare con se un'eventuale info di 
ritorno
unico svantaggio: se il codice dei loop e quello che ci sta dopo sono 
fortemente accoppiati, diventa na schifezza perch� servono un mucchio di 
variabili come parametro.
A questo punto volendo evolvere ulteriormente la cosa si potrebbe 
pensare di far diventare il tutto una classe "locale" dove il contesto 
(e l'accoppiamento) lo metto nelle variabili membro e il loop lo metto 
nel costruttore con la tecnica del return...
Forse c'� un piccolo costo aggiuntivo rispetto ad avere tutte le 
variabili sullo stack dovuto al dereferencing del this? vallo a sapere.
Nota bene che le classi si possono definire anche all'interno delle 
funzioni e allora potrei usare il costruttore come punto in cui viene 
chiamato il loop e non vado neanche ad inquinare nessun file .h
Ma ho paura che na roba del genere risoffra del problema di leggibilit�!
Alla fine continuo a propendere per il goto!
Esempio:
void UnaFunzione()
{
  struct TrovaEFai
  {
   bool trovato;
   int x;
   int y;
   //..tutto il contesto
   TrovaEFai()
   {
    trovato = false;
    for(y...)
    {
     for(x...)
     {
      if(...)
      {
       trovato = true;
       return;
      }
     }
    }
   }
  } varTrovaEFai;
  if(varTrovaEFai.trovato)
    Usa(varTrovaEFai.x, varTrovaEFai.y);
}

 
goto ï¿œ il metodo migliore, ma solo in questi casi...
sta al programmatore non lasciare file aperti, memoria allocata, eccetera...

 quell'accrocchio coi valori booleani.
Non parlare di efficienza senza i numeri alla mano.
Semplicemente sbaglierai, a meno che tu non conosca a menadito:
1. l'architettura hardware che usi
2. il backend del tuo compilatore
3. le ottimizzazioni del tuo compilatore
Senza questa conoscenza (e senza averla a livello maniacale) e' *fuffa*.
Oppure si fa una bella misurazione dei tempi sullo use-case e si decide.
Se i tempi sono accettabili, la soluzione piu' leggibile vince. Se non
lo sono, amen, si prova altro.
-- 
-riko

 OHH RAGAZZI ADESSO POI BASTA :) :) :)
Effettivamente...
-- 
-riko

 sta al programmatore non lasciare file aperti, memoria allocata, eccetera...
BTW, goto segue il corretto schema di allocazione/deallocazione in C++.
In particolare non ti viene consentito di saltare un'inizializzazione,
per esempio.
Il che vuole dire che scrivere buon C++ exception safe fa si che quel
codice si comporti bene anche con i goto (e con qualunque altra cosa)(.
Avere codice che da problemi con goto, significa avere codice che da
problemi se qualcuno lancia un'eccezione.
-- 
-riko

 Se i tempi sono accettabili, 
mah ... e ho garanzie che quel codice sarà sempre compilato 
bene allo stesso modo e che girerà immune su qualsiasi 
macchina ? PEr non sapere niente in anticipo, preferisco un 
codice che sembri razionale e snello già per come è scritto 
(e confido che non venga peggiorato ad hoc).
Per il resto sono d'accordo circa un'ottimizzazione spinta, 
specie se va a scapito di altro. Ma questo non mi pareva il 
caso. Almeno, un goto LOCALE non peggiora quella che io 
percepisco come legittimità. Il return nemmeno, e se avessi 
capito subito l'esempio, non avrei neppure obiettato. E' 
ovio che il costo di una sola chiamata è trascurabile sempre.
ciao
Soviet


 ancora pi� sbagliato: incide sulle eccezioni stesse!
No, non sono d'accordo. Basta usare eccezioni "apposta", documentate per
tale utilizzo e usate appropriatamente. Se rilassiamo la condizione di
"usare le cose appropriatamente", allora di fatto buttiamo C++ in toto,
visto che di spazio per errori ce ne e' tanto.
Il fatto e' che il lancio di un'eccezione *e'* uno statement di
controllo di flusso. Lo e' intrinsecamente e ha *quella* semantica.
Nota che di fatto l'uso locale delle eccezioni e' estremamente simile a
quello che faccio in C per gestire gli errori con goto. Ovvero ho spesso
delle zone di gestione di classi di errori locali (quando possibile) e
in caso di errore ci salto, gestisco l'errore e poi prendo azioni
opportune.
Questo e' considerato "bello" rispetto al goto... non so perche'.
Probabilmente perche' e' comunque piu' controllato il modo di passare
informazione (e soprattutto perche' rende possibile gestire gli errori
non localmente in modo molto piu' semplice dei vari setjmp/longjmp).
Ma di fatto le eccezioni sono solo un altro modo di controllare il
flusso. Poi possiamo discutere quando sia buono stile usarle o meno
(esattamente come per ogni altro statement di controllo di flusso).
-- 
-riko