Risposte interessanti: anche se sono d'accordo con tutte (finora), ci sono possibili connotazioni a questa domanda che sono state finora completamente ignorate.
Se il semplice esempio precedente viene esteso con l'allocazione delle risorse e quindi il controllo degli errori con una potenziale liberazione delle risorse risultante, il quadro potrebbe cambiare.
Considera l' approccio ingenuo che i principianti potrebbero adottare:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Quanto sopra rappresenterebbe una versione estrema dello stile di ritorno prematuramente. Si noti come il codice diventa molto ripetitivo e non manutenibile nel tempo quando la sua complessità aumenta. Al giorno d'oggi le persone potrebbero utilizzare la gestione delle eccezioni per catturarli.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip ha suggerito, dopo aver esaminato l'esempio goto di seguito, di utilizzare un interruttore / custodia break-less all'interno del blocco catch sopra. Si potrebbe cambiare (tipo di (e)) e poi cadere attraverso le free_resourcex()
chiamate, ma questo non è banale e necessita di considerazione progettuale . E ricorda che un interruttore / custodia senza interruzioni è esattamente come il goto con le etichette a margherita di seguito ...
Come ha sottolineato Mark B, in C ++ è considerato un buon stile seguire il principio di Resource Aquisition is Initialization , RAII in breve. L'essenza del concetto è usare l'istanza di oggetti per acquisire risorse. Le risorse vengono quindi liberate automaticamente non appena gli oggetti escono dall'ambito e vengono chiamati i loro distruttori. Per le risorse interdipendenti è necessario prestare particolare attenzione per garantire il corretto ordine di deallocazione e per progettare i tipi di oggetti in modo tale che i dati richiesti siano disponibili per tutti i distruttori.
O nei giorni precedenti l'eccezione potrebbe fare:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Ma questo esempio semplicissimo ha diversi inconvenienti: può essere utilizzato solo se le risorse allocate non dipendono l'una dall'altra (ad esempio non può essere utilizzato per allocare memoria, quindi aprire un filehandle, quindi leggere i dati dall'handle nella memoria ) e non fornisce codici di errore individuali e distinguibili come valori di ritorno.
Per mantenere il codice veloce (!), Compatto e facilmente leggibile ed estensibile, Linus Torvalds ha applicato uno stile diverso per il codice del kernel che si occupa delle risorse, anche usando il famigerato goto in un modo che ha assolutamente senso :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
L'essenza della discussione sulle mailing list del kernel è che la maggior parte delle funzionalità del linguaggio che sono "preferite" rispetto all'istruzione goto sono gotos implicite, come enormi if / else simili ad albero, gestori di eccezioni, istruzioni loop / break / continue, ecc. E i goto nell'esempio precedente sono considerati ok, poiché saltano solo per una piccola distanza, hanno etichette chiare e liberano il codice da altri disordine per tenere traccia delle condizioni di errore. Questa domanda è stata discussa anche qui su stackoverflow .
Tuttavia ciò che manca nell'ultimo esempio è un bel modo per restituire un codice di errore. Stavo pensando di aggiungere un result_code++
dopo ogni free_resource_x()
chiamata e restituire quel codice, ma questo compensa alcuni dei guadagni di velocità dello stile di codifica sopra. Ed è difficile restituire 0 in caso di successo. Forse sono solo privo di fantasia ;-)
Quindi, sì, penso che ci sia una grande differenza nella questione della codifica dei rendimenti prematuri o meno. Ma penso anche che sia evidente solo nel codice più complicato che è più difficile o impossibile da ristrutturare e ottimizzare per il compilatore. Che di solito è il caso una volta che l'allocazione delle risorse entra in gioco.