2009-08-08 15 views
18

Quando si utilizza un linguaggio che ha try/catch/finally, le istruzioni di errore/success/exit di D's sono ancora utili? D non sembra avere finalmente quello che potrebbe spiegare perché quelle affermazioni sono usate in D. Ma con un linguaggio come C# è utile? Sto progettando un linguaggio, quindi se vedo molti professionisti lo aggiungo.Il guasto di D'è fallito/successo/uscita necessaria?

+0

Hai un blog o un qualsiasi tipo di sito? –

+0

Ctrl Alt D-1337: No. Dovresti scrivermi (la mia email è nel mio profilo). Potrei rilasciarlo con un nome diverso. (Ho nomi utente diversi per interessi diversi) –

+5

D ha finalmente – BCS

risposta

39

scope(X) non è necessario allo stesso modo in cui for non è necessario a patto di avere if e goto.

Ecco un esempio Parafrasato da qualche codice che ho scritto oggi:

sqlite3* db; 
sqlite3_open("some.db", &db); 
scope(exit) sqlite3_close(db); 

sqlite3_stmt* stmt; 
sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt); 
scope(exit) sqlite3_finalize(stmt); 

// Lots of stuff... 

scope(failure) rollback_to(current_state); 
make_changes_with(stmt); 

// More stuff... 

return; 

Contrasto questo per utilizzare try/catch:

sqlite3* db; 
sqlite3_open("some.db", &db); 
try 
{ 
    sqlite3_stmt* stmt; 
    sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt); 
    try 
    { 
     // Lots of stuff... 
     try 
     { 
      make_changes_with(stmt); 

      // More stuff... 
     } 
     catch(Exception e) 
     { 
      rollback_to(current_state); 
      throw; 
     } 
    } 
    finally 
    { 
     sqlite3_finalize(stmt); 
    } 
} 
finally 
{ 
    sqlite3_close(db); 
} 

Il codice si è trasformato in spaghetti, diffondendo l'errore recupero in tutto il negozio e costringendo un livello di indentazione per ogni blocco try. La versione che utilizza scope (X) è, a mio parere, significativamente più leggibile e più facile da capire.

+2

Oro, non solo perché dovresti farmi come si può diventare spaghetti, ma anche come il codice sia così specifico che i destutori dovrebbero/non possono essere una soluzione. –

+1

Il primo esempio di codice è molto bello. È una boccata d'aria fresca. Siamo tutti così abituati a leggere la seconda forma tradotta che la prima sembra strana, ma ha la stessa sicurezza e la stessa progressione che fa il codice "early return" o "early throw": considera il problema A, indica la risoluzione, dimentica A e passare a considerare il problema B, indicare la risoluzione, dimenticare B e andare avanti. – seh

9

try/catch/finally forza un livello di nidificazione; le guardie di campo no. Inoltre, ti permettono di scrivere il codice di pulizia nella stessa "area" come codice di allocazione, quindi non più "apri file, scorri fino alla fine della funzione, chiudi il file, scorri fino alla parte superiore della funzione".

Fondamentalmente, però, è solo un più conveniente espressione di try movimentazione/catch/finally eccezione - nulla si può fare con try/catch/finally si può fare con le guardie portata, e invertire.

Ne vale la pena? Sono un fanboy di D (quindi, di parte), ma direi sicuramente.

5

Distinguere il fallimento-uscita dal successo-uscita è abbastanza utile alcune volte - Non ho esperienza del mondo reale con D, ma anche l'istruzione with di Python lo consente, e lo trovo molto utile, ad esempio, per entrambi commit o rollback di una transazione DB che è stata aperta nella parte protetta del corpo.

Quando ho spiegato questa nuova caratteristica Python (è in giro da un po 'ora ;-) ad amici e colleghi che sono guru in C++ e Java ho scoperto che hanno capito immediatamente e hanno visto l'interesse per avere una tale funzionalità (Python ha anche finally, ma questo non aiuta a distinguere il successo dall'errore, proprio come in altri linguaggi [o "Distruzione RAII delle variabili automatiche nel blocco" equivalente di C++]).

+0

Non capisco con. Ho letto questo http://effbot.org/zone/python-with-statement.htm Sembra che consenta l'esecuzione di Ctor e Dtor. Non funziona senza la dichiarazione? non conosco bene Python o cosa sta accadendo in realtà –

+1

Il punto è che viene chiamato il metodo speciale '__exit__' del gestore di contesto con informazioni su quale eccezione viene propagata, se esiste; quindi, un gestore di contesto per le operazioni DB, ad esempio, è in grado di distinguere tra casi di successo, garanzia di un commit DB e guasti, richiedendo un rollback del DB. Stai parlando di un problema completamente diverso: i dtors, di per sé, potrebbero essere eseguiti in un tentativo/finalmente, è sintatticamente più goffo di RAII (guarda QUI!) Ma funzionalmente equivalente. Ma distinguere il successo dal fallimento, in alcuni casi, è CRUCIAL! –

6

Disclaimer Sono anche un fanboy di D.

someRiskyFunctionThatMayThrow(); 
lock(); 
/* we have definitly got the lock so lets active 
a piece of code for exit */ 
scope(exit) 
    freelock(); 

Rispetto a:

try 
{ 
    someRiskyFunctionThatMayThrow(); 
    lock(); 
} 
finally 
{ 
    freeLockIfNotGot(); 
} 
2

@DK, Va sottolineato, in C++ (e Java credo) si potrebbe facilmente utilizzare una classe "anonimo" per compiere la stessa cosa di portata (uscita):

int some_func() 
{ 
    class _dbguard { sqlite3* db; 
        _dbguard(const _dbguard&); _dbguard& operator=(const _dbguard&); 
       public: 
        _dbguard(const char* dbname) { sqlite3_open(dbname, &db);} 
        ~_dbguard() {sqlite3_close(db);} 
        operator sqlite3*() { return db; } 

    } db("dbname"); 
    ... 
} 

E se l'hai fatto più di una volta l'avresti immediatamente trasformato in una classe completa per gestire il RAII per te. È così semplice da scrivere che non riesco a immaginare un programma C++ che usi sqlite (come usato nell'esempio) senza creare classi come CSqlite_DB e CSqlite_Stmt.Infatti l'operatore sqlite3 *() dovrebbe essere anathama e la versione completa sarebbe solo avere metodi che forniscono dichiarazioni:

class CSqlite3_DB { 
    ... 
    CSqlite3_Stmt Prepare(const std::string& sql) { 
     sqlite3_stmt* stmt = 0; 
     try { 
      sqlite3_prepare_v2(db, sql.c_str(), &stmt); 
     } catch (...) {} 
     return stmt; 
    } 
}; 

Per quanto riguarda la domanda iniziale, direi che la risposta è "non proprio". Un corretto rispetto per DRY ti direbbe di prendere quei lunghi blocchi di try/catch/finally e convertirli in classi separate che nascondono il try/catch parti dal resto dove possono (nel caso di scope (failure)) e fa gestione delle risorse trasparente (nel caso dell'ambito (uscita)).

+0

risposta eccellente. –

+1

... anche se sottolineerò che scope (exit) è di circa 200 caratteri in meno rispetto a entrambe le versioni :-) –

+0

In realtà, da quando scrivo questo, preferisco il metodo di D's. Quando pensavo veramente a scope (failure) e scope (successo), ero stato venduto. Il modo RAII non può duplicare queste funzionalità senza il brutto supporto MACRO. Tuttavia, per l'esempio SqlLite, una classe wrapper C++ completa è la soluzione C++ "corretta". – jmucchiello

5

Vale la pena menzionare che scope (exit), scope (failure) e scope (successo) sono anche disponibili per C++.

seguente sintassi è supportata, il caso 1:

try 
{ 
    int some_var=1; 
    cout << "Case #1: stack unwinding" << endl; 
    scope(exit) 
    { 
     cout << "exit " << some_var << endl; 
     ++some_var; 
    }; 
    scope(failure) 
    { 
     cout << "failure " << some_var << endl; 
     ++some_var; 
    }; 
    scope(success) 
    { 
     cout << "success " << some_var << endl; 
     ++some_var; 
    }; 
    throw 1; 
} catch(int){} 

stampe:

Case #1: stack unwinding 
failure 1 
exit 2 

Caso 2:

{ 
    int some_var=1; 
    cout << "Case #2: normal exit" << endl; 
    scope(exit) 
    { 
     cout << "exit " << some_var << endl; 
     ++some_var; 
    }; 
    scope(failure) 
    { 
     cout << "failure " << some_var << endl; 
     ++some_var; 
    }; 
    scope(success) 
    { 
     cout << "success " << some_var << endl; 
     ++some_var; 
    }; 
} 

stampe:

Case #2: normal exit 
success 1 
exit 2 
+0

con tanto codice !? Ho implementato SCOPE_EXIT, SCOPE_SUCCESS e SCOPE_FAIL in 6 righe del codice C++ 11. Tutto ciò che faccio è passare in una funzione di lambda. Ma +1 comunque. In nessun modo ho incluso quelle intestazioni. Molto eccessivo.Ecco 3 linee, le altre 3 sono specifiche per il mio codice http://pastebin.com/zaLZ6fP7 –

+1

@ acidzombie24: 1. La tua soluzione non funziona correttamente, controlla questo: http://ideone.com/IcWMEf C'è nessuna eccezione all'interno di ~ Test(), ma "fallimento" viene stampato. –

+0

@ acidzombie24: 2. Per la soluzione solo C++ 11 è infatti più breve. Ma quella libreria funziona bene anche per C++ 98/C++ 03. La maggior parte del codice è correlata all'emulazione di "lambda" per C++ 98/C++ 03. –