Come apparentemente hai già ipotizzato, sì, C ++ offre le stesse funzionalità senza quel meccanismo. Pertanto, in senso stretto, il meccanismo try
/ finally
non è 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
/ finally
fornisce 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 finalize
che 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' IDisposable
interfaccia, che fornisce un Dispose
metodo che è (almeno vagamente) simile a un distruttore C ++. Mentre questo può essere usato tramite un try
/ finally
like 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 comeIDisposable
using
IDisposable
l'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.