Domanda

Faccio Java da diversi anni, quindi non ho seguito C ++. La clausola definitivamente è stata aggiunta alla gestione delle eccezioni C ++ nella definizione della lingua?

Esiste un linguaggio preferito che imita il tentativo / finalmente di Java?

Sono anche infastidito dal fatto che C ++ non abbia un super tipo finale per tutte le possibili eccezioni che potrebbero essere lanciate - come la classe Throwable di Java.

Posso scrivere:

try {
  // do something
} catch(...) {
  // alas, can't examine the exception
  // can only do cleanup code and perhaps rethrow, ala:
  throw;
}

MODIFICA ADDENDUM:

  

Ho finito per accettare la risposta che   ha ottenuto il maggior numero di voti, ovvero l'utilizzo   distruttori per fare pulizia. Ovviamente,   dai miei commenti, è chiaro io   non sono del tutto d'accordo con quello.   Tuttavia, C ++ è quello che è e così via   lo sforzo di applicazione che ho dentro   mente, ho intenzione di lottare più o meno   aderire alla comunità comune   pratica. Userò le classi di template per   avvolgere risorse che non hanno già   un distruttore di classe (cioè libreria C   risorse), conferendole in tal modo   semantica del distruttore.

NUOVA MODIFICA ADDENDUM:

  

Hmm, invece di definitivamente quindi una chiusura   caratteristica forse? Una chiusura combinata con   Approccio ScopeGuard (vedi uno dei   risposte sotto) sarebbe un modo per   eseguire la pulizia con arbitrario   azioni e accesso alla pulizia   contesto di ambito esterno del codice. La pulizia potrebbe essere eseguita nel modo idioma che si vede nella programmazione di Ruby in cui forniscono blocchi di pulizia quando viene aperta una risorsa. Non è un   caratteristica di chiusura considerata   C ++?

È stato utile?

Soluzione

Sfruttando efficacemente i distruttori. Quando viene generata un'eccezione in un blocco try, qualsiasi oggetto creato al suo interno verrà immediatamente distrutto (e quindi chiamato il suo distruttore).

Questo è diverso da Java dove non hai idea di quando verrà chiamato il finalizzatore di un oggetto.

AGGIORNAMENTO : direttamente dalla bocca del cavallo: Perché C ++ non fornisce un & Quot; infine & Quot; costruire?

Altri suggerimenti

I miei $ .02. Ho programmato in linguaggi gestiti come C # e Java per anni, ma sono stato costretto a passare al C ++ per motivi di velocità. All'inizio non riuscivo a credere come avrei dovuto scrivere due volte la firma del metodo nel file header e poi nel file cpp, e non mi piaceva il fatto che non ci fosse finalmente un blocco, e nessuna garbage collection significava tracciare perdite di memoria ovunque - Accidenti non mi è piaciuto per niente!

Tuttavia, come ho detto, sono stato costretto a usare C ++. Quindi sono stato costretto a impararlo seriamente, e ora ho finalmente capito tutti gli idiomi di programmazione come RAII e ho capito tutte le sottigliezze del linguaggio e simili. Mi ci è voluto un po ', ma ora vedo quanto sia diverso un linguaggio rispetto a C # o Java.

In questi giorni penso che C ++ sia il miglior linguaggio che esista! Sì, posso capire che a volte c'è un po 'di più di ciò che chiamo "pula" (cose apparentemente inutili da scrivere), ma dopo aver effettivamente usato la lingua seriamente, ho cambiato completamente idea.

Avevo sempre perdite di memoria. Scrivevo tutto il mio codice nel file .h perché odiavo la separazione del codice, non riuscivo a capire perché lo facessero! E finivo sempre con stupide dipendenze cicliche da includere, e molte altre. Ero davvero impiccato su C # o Java, per me C ++ è stato un enorme passo in avanti. In questi giorni ho capito. Non ho quasi mai perdite di memoria, mi diverto a separare l'interfaccia e l'implementazione e non ho più problemi con le dipendenze del ciclo.

E non mi manca neanche il blocco finally. Ad essere sincero, la mia opinione è che questi programmatori C ++ di cui parli scrivendo ripetute azioni di pulizia in blocchi catch mi suonano come se fossero solo cattivi programmatori C ++. Voglio dire, non sembra che nessuno degli altri programmatori C ++ in questo thread stia avendo nessuno dei problemi che menzioni. RAII rende davvero ridondante, e semmai è meno lavoro. Scrivi un distruttore e poi non devi mai scriverne un altro alla fine! Beh, almeno per quel tipo.

Con rispetto, quello che penso stia succedendo è che ora sei solo abituato a Java, proprio come ero stato.

La risposta di C ++ è RAII: il distruttore dell'oggetto verrà eseguito quando escono dall'ambito. Sia per un ritorno, per un'eccezione o altro. Se gestisci l'eccezione da qualche altra parte, puoi essere sicuro che tutti gli oggetti dalla funzione chiamata fino al tuo gestore saranno correttamente distrutti facendo chiamare il loro distruttore. Puliranno per te.

Leggi http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization

No, infine, non è stato aggiunto a C ++, né è probabile che sia mai stato aggiunto.

Il modo in cui C ++ utilizza il costruttore / distruttore rende superflua la necessità di

Se si utilizza catch (...) per la pulizia, non si utilizza C ++ correttamente. Il codice di pulizia dovrebbe essere tutto nel distruttore.

Sebbene non sia un requisito per usarlo, C ++ ha un'eccezione std :: Costringere gli sviluppatori a derivare da una classe specifica per utilizzare l'eccezione è contrario alla filosofia Keep C ++ del C ++. È anche il motivo per cui non richiediamo che tutte le classi derivino da Object.

Leggi: Il C ++ supporta i blocchi" finalmente "? (E cos'è questa "RAII" di cui continuo a sentire?)

L'uso di finalmente è più soggetto a errori rispetto ai distruttori per fare pulizia.
Questo perché stai forzando l'utente dell'oggetto a ripulire piuttosto che il progettista / implementatore della classe.

Ok, devo aggiungere una risposta ai punti che hai scritto in un post di risposta separato: (Sarebbe molto più conveniente se lo modificassi nella domanda originale, quindi non finisce in fondo in basso le risposte ad essa.

  

Se tutte le operazioni di pulizia vengono sempre eseguite   i distruttori quindi non ce ne sarebbero bisogno   essere qualsiasi codice di pulizia in un fermo   block - ma C ++ ha blocchi catch dove   vengono eseguite le azioni di pulizia. Davvero   ha un blocco per catturare (...) dove si trova   è possibile solo eseguire azioni di pulizia   (beh, certamente non riesco ad arrivare a nessuno   informazioni sull'eccezione per fare qualsiasi   la registrazione).

catch ha uno scopo completamente separato e come programmatore Java dovresti esserne consapevole. La clausola finally è per & Quot; incondizionata & Quot; azioni di pulizia. Non importa come si esce dal blocco, questo deve essere fatto. La cattura è per la pulizia condizionale. Se viene generato questo tipo di eccezione, dobbiamo eseguire alcune azioni extra.

  

La pulizia in un blocco finally sarà   fare se ci fosse un   eccezione generata o meno - che è   cosa si vuole sempre succedere quando   esiste un codice di pulizia.

Davvero? Se vogliamo che accada sempre per questo tipo (diciamo, quando abbiamo finito vogliamo sempre chiudere una connessione al database), allora perché non la definiamo una volta ? Nel tipo stesso? Rendere la connessione al database chiusa, piuttosto che dover provare / finalmente ogni singolo utilizzo?

Questo è il punto dei distruttori. Garantiscono che ogni tipo è in grado di prendersi cura della propria pulizia, ogni volta che viene utilizzato, senza che il chiamante debba pensarci.

  

Gli sviluppatori C ++ dal primo giorno lo sono stati   afflitto dal dover ripetere la pulizia   azioni che compaiono in blocchi di cattura in   il flusso di codice che si verifica su   uscita riuscita dal blocco try.   I programmatori Java e C # lo fanno e basta   una volta nel blocco finally.

No. I programmatori C ++ non ne sono mai stati afflitti. Programmatori C hanno. E i programmatori C che hanno capito che c ++ aveva delle classi, e poi si sono detti programmatori C ++.

Programma in C ++ e C # quotidianamente e mi sento afflitto dalla ridicola insistenza di C # che devo fornire una clausola finally (o un blocco using) OGNI SINGOLO utilizzo una connessione al database o qualcos'altro che deve essere ripulito.

C ++ mi permette di specificare una volta per tutte quella " ogni volta che abbiamo finito con questo tipo, dovrebbe eseguire queste azioni " ;. Non rischio di dimenticare di liberare memoria. Non rischio di dimenticare di chiudere handle di file, socket o connessioni al database. Perché la mia memoria, i miei handle, socket e connessioni db lo fanno da soli.

Come può mai essere preferibile scrivere codice di pulizia duplicato ogni volta che si utilizza un tipo? Se devi racchiudere il tipo perché non ha un distruttore stesso, hai due semplici opzioni:

  • Cerca una libreria C ++ appropriata che fornisca questo distruttore (suggerimento: Boost)
  • Usa boost :: shared_ptr per avvolgerlo e fornirlo con un funzione personalizzata in fase di esecuzione, specificando la pulizia da eseguire.
  

Quando si scrive il server delle applicazioni   software come server di app Java EE   Glassfish, JBoss, ecc., Vuoi essere   in grado di rilevare e registrare le eccezioni   informazione - al contrario di lasciarlo   cadere sul pavimento. O peggio ancora   il tempo di esecuzione e causare un ingrato   uscita improvvisa del server delle applicazioni.   Ecco perché è molto desiderabile avere   una classe base generale per qualsiasi   possibile eccezione.   E C ++ ha proprio una tale classe. std :: eccezione.

     

Hanno fatto C ++ dai tempi di CFront   e Java / C # gran parte di questo decennio. È   chiaro per vedere che c'è solo un enorme   divario culturale in quanto fondamentalmente   si avvicinano cose simili.

No, non hai mai fatto C ++. Hai terminato CFront o C conclassi. Non C ++. C'è un'enorme differenza. Smetti di chiamare le risposte zoppo e potresti imparare qualcosa sulla lingua che pensavi di conoscere. ;)

Le stesse funzioni di pulizia sono completamente povere. Hanno una bassa coesione, in quanto dovrebbero svolgere una serie di attività correlate solo quando si verificano. Hanno un elevato accoppiamento, in quanto hanno bisogno di modificare i loro interni quando cambiano le funzioni che effettivamente fanno qualcosa. Per questo motivo, sono soggetti a errori.

Il costrutto try ... finally è un framework per le funzioni di cleanup. È un modo incoraggiato dal linguaggio di scrivere codice pessimo. Inoltre, poiché incoraggia a scrivere più volte lo stesso codice di pulizia, mina il principio DRY.

Il modo C ++ è di gran lunga preferibile per questi scopi. Il codice di pulizia per una risorsa viene scritto esattamente una volta, nel distruttore. È nello stesso posto del resto del codice per quella risorsa e quindi ha una buona coesione. Il codice di pulizia non deve essere inserito in moduli non correlati, e quindi ciò riduce l'accoppiamento. È scritto esattamente una volta, quando ben progettato.

Inoltre, il modo C ++ è molto più uniforme. Il C ++, con l'aggiunta del puntatore intelligente, gestisce tutti i tipi di risorse allo stesso modo, mentre Java gestisce bene la memoria e fornisce costrutti inadeguati per rilasciare altre risorse.

Ci sono molti problemi con C ++, ma questo non è uno di questi. Ci sono modi in cui Java è migliore di C ++, ma questo non è uno di questi.

Java sarebbe molto meglio con un modo per implementare RAII invece di provare ... finalmente.

Per evitare di dover definire una classe wrapper per ogni risorsa rilasciabile, potresti essere interessato a ScopeGuard ( http : //www.ddj.com/cpp/184403758 ) che consente di creare " detergenti " al volo.

Ad esempio:

FILE* fp = SomeExternalFunction();
// Will automatically call fclose(fp) when going out of scope
ScopeGuard file_guard = MakeGuard(fclose, fp);

Un esempio di quanto sia difficile usare finalmente correttamente.

Apri e chiudi due file.
Dove si desidera garantire che il file sia chiuso correttamente.
Aspettare il GC non è un'opzione poiché i file possono essere riutilizzati.

In C ++

void foo()
{
    std::ifstream    data("plop");
    std::ofstream    output("plep");

    // DO STUFF
    // Files closed auto-magically
}

In una lingua senza distruttori ma con una clausola finally.

void foo()
{
    File            data("plop");
    File            output("plep");

    try
    {
        // DO STUFF
    }
    finally
    {
        // Must guarantee that both files are closed.
        try {data.close();}  catch(Throwable e){/*Ignore*/}
        try {output.close();}catch(Throwable e){/*Ignore*/}
    }
}

Questo è un semplice esempio e già il codice viene contorto. Qui stiamo solo cercando di mettere a punto 2 risorse semplici. Tuttavia, poiché il numero di risorse che devono essere gestite aumenta e / o la loro complessità aumenta, l'uso di un blocco finalmente diventa sempre più difficile da utilizzare correttamente in presenza di eccezioni.

L'uso di finalmente sposta la responsabilità dell'uso corretto sull'utente di un oggetto. Utilizzando il meccanismo di costruzione / distruzione fornito da C ++, si sposta la responsabilità dell'uso corretto sul progettista / implementatore della classe. Questo è intrinsecamente più sicuro in quanto il progettista deve farlo correttamente solo una volta a livello di classe (piuttosto che avere diversi utenti provano a farlo correttamente in modi diversi).

Utilizzando C ++ 11 con le sue espressioni lambda , ho recentemente ha iniziato a utilizzare il seguente codice per imitare finally:

class FinallyGuard {
private:
  std::function<void()> f_;
public:
  FinallyGuard(std::function<void()> f) : f_(f) { }
  ~FinallyGuard() { f_(); }
};

void foo() {
  // Code before the try/finally goes here
  { // Open a new scope for the try/finally
    FinallyGuard signalEndGuard([&]{
      // Code for the finally block goes here
    });
    // Code for the try block goes here
  } // End scope, will call destructor of FinallyGuard
  // Code after the try/finally goes here
}

FinallyGuard è un oggetto costruito con un argomento simile a una funzione richiamabile, preferibilmente un'espressione lambda. Ricorderà semplicemente quella funzione fino a quando non viene chiamato il suo distruttore, come nel caso in cui l'oggetto esca dall'ambito, a causa del normale flusso di controllo o a causa dello svolgersi dello stack durante la gestione delle eccezioni. In entrambi i casi, il distruttore chiamerà la funzione, eseguendo così il codice in questione.

È un po 'strano che tu debba scrivere il codice per try prima il codice per il blocco std::function<void()>, ma a parte questo in realtà sembra un vero < => / <=> da Java. Immagino che non si debba abusare di ciò per le situazioni in cui un oggetto con il proprio proprio distruttore sarebbe più appropriato, ma ci sono casi in cui considero questo approccio sopra più adatto. Ho discusso di uno di questi scenari in questa domanda .

Per quanto ho capito, <=> utilizzerà una indiretta del puntatore e almeno una chiamata di funzione virtuale per eseguire la sua cancellazione di tipo , quindi ci sarà un sovraccarico delle prestazioni . Non utilizzare questa tecnica in un circuito ristretto in cui le prestazioni sono fondamentali. In quei casi, un oggetto specializzato il cui distruttore fa solo una cosa sarebbe più appropriato.

I

distruttori C ++ rendono finally ridondanti. Puoi ottenere lo stesso effetto spostando finalmente il codice di pulizia dai distruttori corrispondenti.

Penso che ti stia perdendo il punto di cosa catch (...) può fare.

Dici nel tuo esempio " ahimè, non posso esaminare l'eccezione " ;. Bene, non hai informazioni sul tipo di eccezione. Non sai nemmeno se è un tipo polimorfico, quindi anche se avessi una sorta di riferimento non tipizzato ad esso, non potresti nemmeno tentare in sicurezza un dynamic_cast.

Se conosci alcune eccezioni o gerarchie di eccezioni con cui puoi fare qualcosa, allora questo è il posto per blocchi di cattura con tipi di nome espliciti.

try non è spesso utile in C ++. Può essere utilizzato in luoghi che devono garantire di non lanciare, o solo lanciare determinate eccezioni contratte. Se si utilizza catch per la pulizia, è molto probabile che il codice non sia sicuro in modo sicuro rispetto alle eccezioni.

Come menzionato in altre risposte, se stai usando oggetti locali per gestire le risorse (RAII), allora può essere sorprendente e illuminante la quantità di blocchi di cattura di cui hai bisogno, spesso - se non hai bisogno di fare nulla localmente con un'eccezione - anche il blocco try può essere ridondante quando lasci che le eccezioni fluiscano al codice client in grado di rispondere a loro senza garantire problemi di risorse.

Per rispondere alla tua domanda originale, se hai bisogno di un pezzo di codice da eseguire alla fine di un blocco, un'eccezione o nessuna eccezione, allora sarebbe una ricetta.

class LocalFinallyReplacement {
    ~LocalFinallyReplacement() { /* Finally code goes here */ }
};
// ...
{ // some function...
    LocalFinallyReplacement lfr; // must be a named object

    // do something
}

Nota come possiamo eliminare completamente throw, <=> e <=>.

Se nella funzione erano stati dichiarati dati originariamente dichiarati al di fuori del blocco try a cui era necessario accedere tra " infine " blocco, potrebbe essere necessario aggiungerlo al costruttore della classe helper e memorizzarlo fino al distruttore. Tuttavia, a questo punto riconsidererei seriamente se il problema potrebbe essere risolto alterando il design degli oggetti di gestione delle risorse locali in quanto implicherebbe qualcosa di errato nel design.

Non completamente offtopico.

Pulizia della risorsa DB di placcatura della caldaia in Java

modalità sarcasmo: il linguaggio di Java non è meraviglioso?

Ho fatto un sacco di design di classe e design di wrapper di modelli in C ++ in quei 15 anni e ho fatto tutto in modo C ++ in termini di pulizia dei distruttori. Ogni progetto, tuttavia, implicava invariabilmente anche l'uso di librerie C che fornivano risorse per aprirlo, usarlo, chiuderlo come modello di utilizzo. Un tentativo / infine significherebbe che una tale risorsa può essere semplicemente consumata dove deve essere - in un modo completamente robusto - ed essere eseguita con essa. L'approccio meno noioso alla programmazione di tale situazione. Potrebbe occuparsi di tutti gli altri stati in corso durante la logica di quella pulizia senza dover essere trascinati via in qualche distruttore wrapper.

Ho eseguito la maggior parte della mia codifica C ++ su Windows, quindi potrei sempre ricorrere all'utilizzo di Microsoft __try / __ finalmente per tali situazioni. (La loro gestione strutturata delle eccezioni ha alcune potenti capacità di interagire con le eccezioni.) Purtroppo, il linguaggio C non sembra aver mai ratificato alcun costrutto portatile per la gestione delle eccezioni.

Quella non era la soluzione ideale, perché non era semplice fondere codice C e C ++ in un blocco try in cui si poteva generare uno stile di eccezione. Un blocco finalmente aggiunto al C ++ sarebbe stato utile per quelle situazioni e avrebbe consentito la portabilità.

Per quanto riguarda il tuo addendum-edit, si stanno prendendo in considerazione le chiusure per C ++ 0x. Possono essere utilizzati con protezioni con ambito RAII per fornire una soluzione facile da usare, controllare blog di Pizer . Possono anche essere usati per imitare try-finally, vedi questa risposta ; ma è davvero una buona idea? .

Ho pensato di aggiungere la mia soluzione a questo: una specie di wrapper di puntatore intelligente per quando devi affrontare tipi non RAII.

Usato in questo modo:

Finaliser< IMAPITable, Releaser > contentsTable;
// now contentsTable can be used as if it were of type IMAPITable*,
// but will be automatically released when it goes out of scope.

Quindi ecco l'implementazione di Finaliser:

/*  Finaliser
    Wrap an object and run some action on it when it runs out of scope.
    (A kind of 'finally.')
    * T: type of wrapped object.
    * R: type of a 'releaser' (class providing static void release( T* object )). */
template< class T, class R >
class Finaliser
{
private:
    T* object_;

public:
    explicit Finaliser( T* object = NULL )
    {
        object_ = object;
    }

    ~Finaliser() throw()
    {
        release();
    }

    Finaliser< T, R >& operator=( T* object )
    {
        if (object_ != object && object_ != NULL)
        {
            release();
        }
        object_ = object;

        return *this;
    }

    T* operator->() const
    {
        return object_;
    }

    T** operator&()
    {
        return &object_;
    }

    operator T*()
    {
        return object_;
    }

private:
    void release() throw()
    {
        R::release< T >( object_ );
    }
};

... ed ecco Releaser:

/*  Releaser
    Calls Release() on the object (for use with Finaliser). */
class Releaser
{
public:
    template< class T > static void release( T* object )
    {
        if (object != NULL)
        {
            object->Release();
        }
    }
};

Ho alcuni tipi diversi di releaser come questo, incluso uno gratuito () e uno per CloseHandle ().

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top