Nelle lezioni precedenti C++ sono stati introdotti i costrutti for
, while
e do-while
per il controllo di flusso nell'ambito della programmazione stutturata.
Tali costrutti sono corredati dalle istruzioni break
e continue
, che li rendono ancora più duttili per le esigenze dello sviluppatore, in quanto consentono rispettivamente di anticipare l'uscita dal ciclo, o di saltare un'iterazione.
break
L'effetto dell'istruzione break
nel contesto delle istruzioni di controllo iterative è consistente con quello riscontrato nel costrutto switch
. Infatti essa, se eseguita, provoca l'immediata terminazione del ciclo e l'uscita dall'ambito di visibilità ad esso connesso. A seguito di ciò, il controllo di flusso viene quindi rediretto all'istruzione successiva esterna al ciclo.
Solitamente l'esecuzione di break
è subordinata alla valutazione di una espressione booleana che determina l'uscita dal ciclo in caso di circostanze "straordinarie" rispetto quelle per cui è prevista la naturale terminazione del ciclo, come ad esempio mostrato nel frammento di codice seguente:
int processNextDataChunck();
bool processingCanceledByUser();
int progress = 0;
while (progress < 100)
{
// elaborazione dei dati
progress = processNextDataChunck();
// controllo dell'interazione utente
if (processingCanceledByUser())
break;
// feedback UI
updateProgressBar(progress);
}
Si supponga che l'istanza di dati da elaborare sia suddivisa in sottoparti, chunks, che vengono processate in sequenza dalla funzione processNextDataChunck(). Il valore da essa restituito ad ogni iterazione è la percentuale complessiva di completamento dell'operazione. Tale valore, progress, viene usato sia per determinare quando l'elaborazione è completa, sia per aggiornare una barra di progresso.
L'istruzione break
consente di interrompere l'elaborazione su input dell'utente, ad esempio quando si fa click su un pulsante di annullamento, evento che si suppone venga registrato dalla funzione processingCanceledByUser().
In questa evenienza, l'istruzione corrispondente all'invocazione della funzione updateProgressBar() non verrebbe eseguita, e l'elaborazione dei dati verrebbe interrotta prima del suo completamento (quando cioè progress >= 100) con la terminazione prematura del ciclo while
.
continue
L'istruzione continue
ha come effetto l'interruzione dell'iterazione corrente. Il controllo di flusso rimane confinato all'interno del ciclo, ma viene reindirizzato all'iterazione successiva in conseguenza di una circostanza inattesa che invalida o rende superflua l'esecuzione di tale iterazione.
Supponiamo ad esempio di modificare come segue il frammento precendente con l'introduzione della funzione getNextDataChunckSize(). Essa restituisce la dimensione in byte della prossima porzione di dati da elaborare, pertanto questa informazione può essere usata per prevenire l'invocazione della funzione processNextDataChunck() su un'istanza di dati nulla.
int getNextDataChunckSize();
void skipToNextDataChunk();
int processNextDataChunck();
bool processingCanceledByUser();
int progress = 0;
while (progress < 100)
{
// controlla che la porzione di dati non sia vuota
if (getNextDataChunckSize() == 0)
{
skipToNextDataChunk();
continue;
}
// elaborazione dei dati
progress = processNextDataChunck();
// controllo dell'interazione utente
if (processingCanceledByUser())
break;
// feedback UI
updateProgressBar(progress);
}
Quando la dimensione del prossimo chunk è 0, viene invocata la funzione skipToNextDataChunk() per modificare lo stato del programma in modo da saltare alla prossima porzione di dati e, a seguito di ciò, l'istruzione continue
reindirizza il flusso all'inizio dell'iterazione successiva.
Cicli annidati
Nel caso di cicli annidati, l'effetto delle istruzioni break
e continue
vale soltanto per il ciclo in cui esse sono contenute e non si propaga al di fuori di esso.
Ad esempio nel frammento seguente, l'output prodotto su schermo includerà tutti valori assunti dalla variabile i (da 0 a 9), ma solo quelli in cui j != i per ogni iterazione del ciclo interno.
for (int i=0; i<10; i++)
{
for (int j=0; j<10; j++)
{
if (i == j)
continue;
std::cout << i << std::endl;
}
std::cout << i << std::endl;
}
Pratiche di buona programmazione
L'uso di break
e continue
dovrebbe essere limitato ai soli casi in cui è effettivamente necessario, o particolarmente conveniente in termini di semplicità di implementazione e leggigiblità del codice.
Ad esempio è consigliable quando l'esecuzione del programma ne benficia in termini di prestazioni, evitando l'esecuzione di operazioni superflue e potenzialmente onerose, o in termini di stabilità, ad esempio prevenendo l'iterazione di un blocco di istruzioni su un'istanza malformata.
Tuttavia, è sempre bene cercare di mantenere il controllo di flusso quanto più possile lineare a beneficio della manutenibilità del codice, evitando un uso eccessivo di break
e continue
.