La parte "finalmente" di un costrutto "prova ... cattura ... finalmente" è persino necessaria?


25

Alcuni linguaggi (come C ++ e le prime versioni di PHP) non supportano la finallyparte di un try ... catch ... finallycostrutto. È finallymai necessario? Poiché il codice in esso viene sempre eseguito, perché non dovrei / non dovrei semplicemente inserire quel codice dopo un try ... catchblocco senza una finallyclausola? Perché usarne uno? (Sto cercando un motivo / motivazione per usare / non usare finally, non un motivo per eliminare 'catture' o perché è legale farlo.)


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

Risposte:


36

Oltre a quanto affermato da altri, è anche possibile che venga generata un'eccezione all'interno della clausola catch. Considera questo:

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

In questo esempio, la Cleanup()funzione non viene mai eseguita, poiché viene rilevata un'eccezione nella clausola catch e la successiva cattura più in alto nello stack di chiamate la rileva. L'uso di un blocco finally elimina questo rischio e rende il codice più pulito per l'avvio.


4
Grazie per una risposta concisa e diretta che non divaga in teoria e il territorio "linguaggio X è migliore di Y".
Agi Hammerthief,

56

Come altri hanno già detto, non esiste alcuna garanzia che il codice tryverrà eseguito dopo un'istruzione se non si rileva ogni possibile eccezione. Detto questo, questo:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

può essere riscritto 1 come:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

Ma quest'ultimo richiede di catturare tutte le eccezioni non gestite, duplicare il codice di pulizia e ricordarsi di rilanciare. Quindi finallynon è necessario , ma è utile .

Il C ++ non ha finallyperché Bjarne Stroustrup ritiene che RAII sia migliore , o almeno sia sufficiente per la maggior parte dei casi:

Perché il C ++ non fornisce un costrutto "finalmente"?

Perché C ++ supporta un'alternativa che è quasi sempre migliore: la tecnica "acquisizione delle risorse è inizializzazione" (TC ++ PL3 sezione 14.4). L'idea di base è quella di rappresentare una risorsa da un oggetto locale, in modo che il distruttore dell'oggetto locale rilasci la risorsa. In questo modo, il programmatore non può dimenticare di rilasciare la risorsa.


1 Il codice specifico per rilevare tutte le eccezioni e ricodificarle senza perdere le informazioni sulla traccia dello stack varia in base alla lingua. Ho usato Java, dove viene catturata la traccia dello stack quando viene creata l'eccezione. In C # useresti semplicemente throw;.


8
Devi anche cogliere le eccezioni nel handleError()secondo caso, no?
Juri Robl,

1
Potresti anche generare un errore. Lo riformulerei catch (Throwable t) {}, con il tentativo ... cattura blocco attorno all'intero blocco iniziale (per catturare anche i lanciatori handleError)
njzk2,

1
In realtà aggiungerei il extra-catch che hai omesso durante la chiamata, il handleErro();che renderà l'argomento ancora migliore sul perché finalmente i blocchi sono utili (anche se non era la domanda originale).
Alex,

1
Questa risposta non affronta davvero la domanda sul perché il C ++ non ha finally, che è molto più sfumato.
DeadMG

1
@AgiHammerthief Il nidificata tryè all'interno catchdella specifica eccezione. In secondo luogo, è possibile che tu non sappia se puoi gestire correttamente l'errore fino a quando non hai esaminato l'eccezione o che la causa dell'eccezione ti impedisce anche di gestire l'errore (almeno a quel livello). È abbastanza comune quando si esegue l'I / O. Il ripensamento è lì perché l'unico modo per garantire le cleanUpesecuzioni è catturare tutto , ma il codice originale consentirebbe alle eccezioni originate nel catch (SpecificException e)blocco di propagarsi verso l'alto.
Doval

22

finally i blocchi vengono generalmente utilizzati per chiarire le risorse che possono aiutare con la leggibilità quando si utilizzano più istruzioni di restituzione:

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

vs

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}

2
Penso che questa sia la risposta migliore. L'uso di un finally come sostituto di un'eccezione generica sembra merdoso. Il caso d'uso corretto è di ripulire risorse o operazioni analoghe.
Kik,

3
Forse è ancora più comune tornare all'interno del blocco try, anziché all'interno del blocco catch.
Michael Anderson,

A mio avviso, il codice non spiega adeguatamente l'uso di finally. (
Userei il

15

Come apparentemente hai già ipotizzato, sì, C ++ offre le stesse funzionalità senza quel meccanismo. Pertanto, in senso stretto, il meccanismo try/ finallynon è realmente necessario.

Detto questo, farne a meno impone alcuni requisiti sul modo in cui è progettato il resto della lingua. In C ++ la stessa serie di azioni è incarnata in un distruttore di classe. Funziona principalmente (esclusivamente?) Perché l'invocazione del distruttore in C ++ è deterministica. Questo, a sua volta, porta ad alcune regole piuttosto complesse sulla durata degli oggetti, alcune delle quali sono decisamente non intuitive.

La maggior parte delle altre lingue offre invece una qualche forma di raccolta dei rifiuti. Mentre ci sono cose sulla raccolta dei rifiuti che sono controverse (ad esempio, la sua efficienza rispetto ad altri metodi di gestione della memoria) una cosa generalmente non lo è: il momento esatto in cui un oggetto verrà "ripulito" dal garbage collector non è legato direttamente nell'ambito dell'oggetto. Ciò ne impedisce l'uso quando la pulizia deve essere deterministica o quando è semplicemente necessaria per il corretto funzionamento o quando si tratta di risorse così preziose da non ritardare arbitrariamente la loro pulizia.try/ finallyfornisce un modo per tali lingue di affrontare quelle situazioni che richiedono quella pulizia deterministica.

Penso che quelli che sostengono che la sintassi C ++ per questa capacità sia "meno amichevole" di quella di Java manchino piuttosto il punto. Peggio ancora, manca un punto molto più cruciale sulla divisione delle responsabilità che va ben oltre la sintassi e ha molto più a che fare con la progettazione del codice.

In C ++, questa pulizia deterministica avviene nel distruttore dell'oggetto. Ciò significa che l'oggetto può essere (e normalmente dovrebbe essere) progettato per ripulire dopo se stesso. Questo è l'essenza del design orientato agli oggetti: una classe dovrebbe essere progettata per fornire un'astrazione e applicare i propri invarianti. In C ++, uno fa esattamente questo - e uno degli invarianti per i quali prevede è che quando l'oggetto viene distrutto, le risorse controllate da quell'oggetto (tutte, non solo la memoria) verranno distrutte correttamente.

Java (e simili) sono leggermente diversi. Mentre supportano (una sorta di) un supporto finalizeche potrebbe teoricamente fornire capacità simili, il supporto è così debole che è praticamente inutilizzabile (e in effetti, praticamente mai usato).

Di conseguenza, piuttosto che la classe stessa sia in grado di eseguire la pulizia richiesta, il client della classe deve prendere delle misure per farlo. Se facciamo un confronto sufficientemente miope, a prima vista può sembrare che questa differenza sia piuttosto minore e Java sia abbastanza competitiva con il C ++ in questo senso. Finiamo con qualcosa del genere. In C ++, la classe è simile alla seguente:

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... e il codice client è simile al seguente:

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

In Java scambiamo un po 'più di codice in cui l'oggetto viene usato per un po' meno nella classe. Questo inizialmente sembra un compromesso piuttosto uniforme. In realtà, però, è tutt'altro, perché nel codice più tipico definiamo la classe solo in un posto, ma la usiamo in molti posti. L'approccio C ++ significa che scriviamo quel codice solo per gestire la pulizia in un unico posto. L'approccio Java significa che dobbiamo scrivere quel codice per gestire la pulizia più volte, in molti posti - ogni posto in cui utilizziamo un oggetto di quella classe.

In breve, l'approccio Java sostanzialmente garantisce che molte astrazioni che proviamo a fornire siano "trapelate": ogni classe che richiede una pulizia deterministica obbliga il cliente della classe a conoscere i dettagli di cosa pulire e come fare la pulizia , anziché nascondere quei dettagli nella classe stessa.

Anche se l'ho chiamato "l'approccio Java" sopra, try/finally e meccanismi simili con altri nomi non sono interamente limitati a Java. Per un esempio importante, la maggior parte (tutti?) Dei linguaggi .NET (ad esempio, C #) forniscono lo stesso.

Le recenti iterazioni di Java e C # forniscono anche una sorta di punto intermedio tra Java "classico" e C ++ a questo proposito. In C #, un oggetto che vuole automatizzare la sua pulizia può implementare l' IDisposableinterfaccia, che fornisce un Disposemetodo che è (almeno vagamente) simile a un distruttore C ++. Mentre questo può essere usato tramite un try/ finallylike in Java, C # automatizza un po 'di più il compito con a di disporre della classe nella sua implementazione di . Tutto ciò che resta per il programmatore client è il minore onere di scrivere una dichiarazione per assicurare cheusing un'istruzione che consente di definire le risorse che verranno create quando viene inserito un ambito e distrutte quando si esce dall'ambito. Sebbene sia ancora ben al di sotto del livello di automazione e certezza fornito da C ++, questo è ancora un miglioramento sostanziale rispetto a Java. In particolare, il progettista di classe può centralizzare i dettagli di comeIDisposableusingIDisposablel'interfaccia verrà utilizzata quando dovrebbe essere. In Java 7 e versioni successive, i nomi sono stati cambiati per proteggere i colpevoli, ma l'idea di base è sostanzialmente identica.


1
Risposta perfetta Distruttori sono IL caratteristica must-have in C ++.
Thomas Eding,

13

Non riesco a credere che nessun altro abbia sollevato questo (nessun gioco di parole previsto) - non hai bisogno di un fermo clausola di !

Questo è perfettamente ragionevole:

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

Nessuna clausola di cattura da nessuna parte è vista, perché questo metodo non può fare nulla di utile con queste eccezioni; vengono lasciati propagare il backup dello stack di chiamate a un gestore che può farlo . Catturare e rilanciare le eccezioni in ogni metodo è una cattiva idea, soprattutto se si sta semplicemente rilanciando la stessa eccezione. Va completamente contro il modo in cui dovrebbe funzionare la gestione delle eccezioni strutturata (ed è abbastanza vicino alla restituzione di un "codice di errore" da ogni metodo, proprio nella "forma" di un'eccezione).

Ciò che questo metodo non ha a che fare, però, per pulire dopo se stessa, in modo che il "Outside World" non ha bisogno di sapere nulla del caos che esso stesso ottenuto in. La clausola finally fa proprio questo - indipendentemente da come si comportano i metodi chiamati, la clausola finally verrà eseguita "all'uscita" del metodo (e lo stesso vale per ogni clausola finally tra il punto in cui viene generata l'eccezione e l'eventuale clausola di cattura che la gestisce); ognuno viene eseguito mentre lo stack di chiamate "si svolge".


9

Cosa succederebbe se si generasse un'eccezione che non ti aspettavi. Il tentativo terminerebbe nel mezzo di esso e non viene eseguita alcuna clausola catch.

L'ultimo blocco è quello di aiutare con questo e garantire che, indipendentemente dall'eccezione, avvenga la pulizia.


4
Questo non è un motivo sufficiente per a finally, in quanto è possibile prevenire eccezioni "impreviste" con catch(Object)o catch(...)catch-alls.
MSalters il

1
Che suona come una soluzione. Concettualmente finalmente è più pulito. Anche se devo confessare di usarlo raramente.
quick_now

7

Alcuni linguaggi offrono sia costruttori che distruttori per i loro oggetti (es. C ++ credo). Con queste lingue puoi fare la maggior parte (senza dubbio tutte) di ciò che di solito viene fatto finallyin un distruttore. Come tale - in quelle lingue - afinally clausola può essere superflua.

In un linguaggio senza distruttori (ad esempio Java) è difficile (forse addirittura impossibile) ottenere una corretta pulizia senza la finallyclausola. NB - In Java esiste un finalisemetodo ma non esiste alcuna garanzia che verrà mai chiamato.


Può essere utile notare che i distruttori aiutano nella pulizia delle risorse quando la distruzione è deterministica . Se non sappiamo quando l'oggetto verrà distrutto e / o spazzatura, i distruttori non saranno abbastanza sicuri.
Morwenn,

@Morwenn - Buon punto. Ho accennato a ciò con il mio riferimento a Java, finalisema preferirei non entrare negli argomenti politici sui distruttori / finalisti in questo momento.
OldCurmudgeon,

In C ++ la distruzione è deterministica. Quando esce l'ambito contenente un oggetto automatico (ad es. Viene espulso dalla pila), viene chiamato il suo distruttore. (C ++ ti consente di allocare oggetti nello stack, non solo l'heap.)
Rob K

@RobK - E questa è l'esatta funzionalità di un finalisema con sia un sapore estensibile che un meccanismo simile a oop - molto espressivo e paragonabile al finalisemeccanismo di altre lingue.
OldCurmudgeon,

1

Prova finalmente e prova a catturare sono due cose diverse che condividono solo la parola chiave "provare". Personalmente mi piacerebbe vederlo diverso. Il motivo per cui li vedi insieme è perché le eccezioni producono un "salto".

E prova finalmente è progettato per eseguire il codice anche se il flusso di programmazione salta fuori. Che si tratti di un'eccezione o di qualsiasi altra ragione. È un modo semplice per acquisire una risorsa e assicurarsi che venga ripulito dopo senza doversi preoccupare dei salti.


3
In .NET sono implementati usando meccanismi separati; in Java, tuttavia, l'unico costrutto riconosciuto dalla JVM è semanticamente equivalente a "on error goto", un modello che supporta direttamente try catchma non try finally; il codice che usa quest'ultimo viene convertito in codice usando solo il primo, copiando il contenuto del finallyblocco in tutti gli spazi del codice dove potrebbe essere necessario eseguirlo.
supercat

@supercat nice, grazie per le informazioni extra su Java.
Pieter B,

1

Poiché questa domanda non specifica C ++ come linguaggio, prenderò in considerazione un mix di C ++ e Java, poiché adottano un approccio diverso alla distruzione degli oggetti, che viene suggerito come una delle alternative.

Motivi per cui potresti usare un blocco finally, piuttosto che codice dopo il blocco try-catch

  • torni presto dal blocco try: considera questo

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    paragonato a:

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • torni presto dai blocchi di cattura: confronta

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • Riscopri le eccezioni. Confrontare:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

Questi esempi non fanno sembrare troppo male, ma spesso hai molti di questi casi che interagiscono e più di un tipo di eccezione / risorsa in gioco. finallypuò aiutare a evitare che il codice diventi un incubo aggrovigliato di manutenzione.

Ora in C ++ questi possono essere gestiti con oggetti basati sull'ambito. Ma IMO presenta due svantaggi di questo approccio 1. La sintassi è meno amichevole. 2. L'ordine di costruzione che è il contrario dell'ordine di distruzione può rendere le cose meno chiare.

In Java non puoi agganciare il metodo finalize per eseguire la pulizia poiché non sai quando accadrà - (beh puoi ma questo è un percorso pieno di divertenti condizioni di gara - JVM ha un sacco di possibilità nel decidere quando distrugge cose - spesso non è quando te lo aspetti - prima o poi di quanto potresti aspettarti - e ciò può cambiare mentre il compilatore hot-spot entra in ... sigh ...)


1

Tutto ciò che è logicamente "necessario" in un linguaggio di programmazione sono le istruzioni:

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

Qualsiasi algoritmo può essere implementato usando solo le istruzioni sopra, tutti gli altri costrutti del linguaggio sono lì per rendere i programmi più facili da scrivere e più comprensibili per gli altri programmatori.

Guarda il vecchio computer del mondo per l'hardware reale usando un set di istruzioni così minimo.


1
La tua risposta è certamente vera, ma non codifico in assembly; è troppo doloroso. Sto chiedendo perché usare una funzione che non vedo il punto in lingue che la supportano, non quale sia il minimo set di istruzioni di una lingua.
Agi Hammerthief,

1
Il punto è che qualsiasi linguaggio implementando solo queste 5 operazioni può implementare qualsiasi algoritmo, anche se in modo piuttosto tortuoso. La maggior parte dei vers / operatori in linguaggi di alto livello non sono "necessari" se l'obiettivo è semplicemente implementare un algoritmo. Se l'obiettivo è avere un rapido sviluppo di codici leggibili leggibili, la maggior parte sono necessari ma "leggibili" e "mantenibili" non sono misurabili ed estremamente soggettivi. I simpatici sviluppatori di lingue mettono molte funzionalità: se non ne hai una per alcune, non usarle.
James Anderson,

0

In realtà il divario più grande per me è di solito nelle lingue che supportano finallyma mancano di distruttori, perché puoi modellare tutta la logica associata a "cleanup" (che separerò in due categorie) attraverso i distruttori a livello centrale senza occuparsi manualmente di cleanup logica in ogni funzione rilevante. Quando vedo C # o codice Java fare cose come sbloccare manualmente i mutex e chiudere i file in finallyblocchi, mi sembra obsoleto e un po 'come il codice C quando tutto ciò è automatizzato in C ++ attraverso i distruttori in modo da liberare gli umani da quella responsabilità.

Tuttavia, troverei comunque una leggera comodità se incluso C ++ finallyed è perché ci sono due tipi di pulizie:

  1. Distruggere / liberare / sbloccare / chiudere / ecc. Risorse locali (i distruttori sono perfetti per questo).
  2. Annullare / ripristinare gli effetti collaterali esterni (i distruttori sono adeguati per questo).

Il secondo almeno non si associa in modo così intuitivo all'idea della distruzione delle risorse, anche se puoi farlo bene con le protezioni dell'ambito che ripristinano automaticamente le modifiche quando vengono distrutte prima di essere impegnate. Ci finallyfornisce probabilmente almeno un po ' (solo con un po' teeny) meccanismo più semplice per il lavoro di guardie portata.

Tuttavia, un meccanismo ancora più semplice sarebbe un rollbackblocco che non avevo mai visto prima in nessuna lingua. È una specie di mio sogno irrealizzabile se avessi mai progettato un linguaggio che prevedesse la gestione delle eccezioni. Sarebbe simile a questo:

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

Sarebbe il modo più semplice per modellare i rollback degli effetti collaterali, mentre i distruttori sono praticamente il meccanismo perfetto per la pulizia delle risorse locali. Ora salva solo un paio di righe di codice in più dalla soluzione scope guard, ma il motivo per cui voglio così tanto vedere una lingua con questo è che il rollback degli effetti collaterali tende ad essere l'aspetto più trascurato (ma più complicato) della gestione delle eccezioni nelle lingue che ruotano attorno alla mutabilità. Penso che questa funzione incoraggerebbe gli sviluppatori a pensare alla gestione delle eccezioni nel modo corretto in termini di rollback delle transazioni ogni volta che le funzioni causano effetti collaterali e non si completano e, come bonus laterale quando le persone vedono quanto sia difficile eseguire correttamente i rollback, potrebbero preferire la scrittura di più funzioni prive di effetti collaterali in primo luogo.

Ci sono anche alcuni casi oscuri in cui si desidera semplicemente fare varie cose, indipendentemente da come si esce da una funzione, indipendentemente da come è uscita, come magari registrare un timestamp. C'è finallyprobabilmente la soluzione più semplice e perfetta per il lavoro, dal momento che provare a creare un'istanza di un oggetto solo per usare il suo distruttore al solo scopo di registrare un timestamp è davvero strano (anche se puoi farlo bene e abbastanza comodamente con lambdas ).


-9

Come tante altre cose insolite sul linguaggio C ++, la mancanza di un try/finallycostrutto è un difetto di progettazione, se si può anche chiamarlo in un linguaggio che spesso sembra non aver fatto alcun lavoro di progettazione .

RAII (l'uso dell'invocazione del distruttore deterministico basato sull'ambito su oggetti basati su stack per la pulizia) presenta due gravi difetti. Il primo è che richiede l'uso di oggetti basati su stack , che sono un abominio che viola il principio di sostituzione di Liskov. Ci sono molte buone ragioni per cui nessun altro linguaggio OO prima o dopo che C ++ li ha usati - all'interno di epsilon; D non conta perché è fortemente basato sul C ++ e non ha comunque quote di mercato - e spiegare i problemi che causano va oltre lo scopo di questa risposta.

In secondo luogo, ciò che finallypuò fare è un superset di distruzione di oggetti. Molto di ciò che viene fatto con RAII in C ++ verrebbe descritto nel linguaggio Delphi, che non ha garbage collection, con il seguente schema:

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

Questo è il modello RAII reso esplicito; se dovessi creare una routine C ++ che contenga solo l'equivalente della prima e terza riga sopra, ciò che il compilatore genererebbe finirebbe per assomigliare a quello che ho scritto nella sua struttura di base. E poiché è l'unico accesso al try/finallycostrutto fornito dal C ++, gli sviluppatori C ++ finiscono per avere una visione piuttosto miopica di try/finally: quando tutto ciò che hai è un martello, tutto inizia a sembrare un distruttore, per così dire.

Ma ci sono altre cose che uno sviluppatore esperto può fare con un finallycostrutto. Non si tratta di distruzione deterministica, anche di fronte a un'eccezione sollevata; riguarda l' esecuzione di codice deterministico , anche a fronte di un'eccezione sollevata.

Ecco un'altra cosa che potresti vedere comunemente nel codice Delphi: un oggetto set di dati con controlli utente associati ad esso. Il set di dati contiene dati da un'origine esterna e i controlli riflettono lo stato dei dati. Se stai per caricare un sacco di dati nel tuo set di dati, vorrai disabilitare temporaneamente l'associazione dei dati in modo che non faccia cose strane alla tua UI, provando ad aggiornarlo più volte con ogni nuovo record inserito , quindi dovresti codificarlo in questo modo:

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

Chiaramente, non c'è nessun oggetto che viene distrutto qui, e non c'è bisogno di uno. Il codice è semplice, conciso, esplicito ed efficiente.

Come si farebbe in C ++? Bene, prima dovresti codificare un'intera classe . Probabilmente sarebbe chiamato DatasetEnablero qualcosa del genere. Tutta la sua esistenza sarebbe come un aiuto RAII. Quindi dovresti fare qualcosa del genere:

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

Sì, quelle parentesi graffe apparentemente superflue sono necessarie per gestire il corretto scoping e garantire che il set di dati venga riattivato immediatamente e non alla fine del metodo. Quindi, ciò che si ottiene non richiede meno righe di codice (a meno che non si utilizzino parentesi graffe egiziane). Richiede la creazione di un oggetto superfluo, che ha un sovraccarico. (Il codice C ++ non dovrebbe essere veloce?) Non è esplicito, ma si basa invece sulla magia del compilatore. Il codice che viene eseguito non è descritto da nessuna parte in questo metodo, ma risiede invece in una classe completamente diversa, possibilmente in un file completamente diverso . In breve, non è in alcun modo una soluzione migliore di poter scrivere il try/finallyblocco da soli.

Questo tipo di problema è abbastanza comune nella progettazione del linguaggio che c'è un nome per esso: inversione di astrazione. Si verifica quando un costrutto di alto livello è costruito sopra un costrutto di basso livello e quindi il costrutto di basso livello non è direttamente supportato nella lingua, richiedendo a coloro che desiderano utilizzarlo di implementarlo nuovamente in termini di costrutto di alto livello, spesso a forti sanzioni sia per la leggibilità del codice che per l'efficienza.


I commenti hanno lo scopo di chiarire o migliorare una domanda e una risposta. Se desideri avere una discussione su questa risposta, vai nella chat room. Grazie.
maple_shaft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.