2015-10-10 13 views
9

Stavo leggendo sulla dichiarazione defer della lingua. Ti consente di specificare un'azione da eseguire quando una funzione è terminata. Ad esempio, se si dispone di un puntatore o risorsa di file, invece di scrivere liberamente/eliminare con ogni possibile percorso di ritorno, è sufficiente specificare la funzione di rinvio una volta."defer" in stile golan in C++

Sembra che un analogo potrebbe arrivare a C++ (What is standard defer/finalizer implementation in C++?, Will there be standardization of scope guard/scope exit idioms?) Fino ad allora, c'è qualcosa di imprevisto nel farlo con un oggetto il cui distruttore effettua una richiamata? Sembra che sia lo destructor order for local variables is sane e che gestisca bene anche le eccezioni, anche se forse non sta uscendo sui segnali.

Ecco un'implementazione di esempio ... c'è qualcosa di preoccupante a riguardo?

#include <iostream> 
#include <functional> 
using namespace std; 

class FrameExitTask { 
    std::function<void()> func_; 
public: 
    FrameExitTask(std::function<void()> func) : 
    func_(func) { 
    } 
    ~FrameExitTask() { 
     func_(); 
    } 
    FrameExitTask& operator=(const FrameExitTask&) = delete; 
    FrameExitTask(const FrameExitTask&) = delete; 
}; 

int main() { 
    FrameExitTask outer_task([](){cout << "world!";}); 
    FrameExitTask inner_task([](){cout << "Hello, ";}); 
    if (1+1 == 2) 
     return -1; 
    FrameExitTask skipped_task([](){cout << "Blam";}); 
} 

uscita: Hello, world!

+2

Questo potrebbe essere più appropriato per [CodeReview] (http://codereview.stackexchange.com). Nota che ci sono già molte implementazioni di questa classe simile a ScopeGuard, quindi perché reinventare la ruota? Alcune implementazioni che conosco hanno vari vantaggi o strumenti specializzati che preferirei alla tua versione (ad esempio, nessun tipo di cancellazione). – dyp

+2

Il problema con questo è che ogni classe dovrebbe già fare ciò che si sta facendo nel proprio distruttore, quindi i casi d'uso in C++ sono molto più rari che in Go. Se stai cercando implementazioni esistenti, controlla boost.ScopeExit. La follia di Facebook ne ha una anche io penso. – inf

+1

Probabilmente stai meglio contrassegnando il distruttore "noexcept". Se la funzione che usi 'FrameExitTask' nei ritorni normalmente, probabilmente un'eccezione dal tuo handler finale funzionerà. Se la funzione termina a causa di qualche altra eccezione, una seconda eccezione dal gestore causerà problemi. –

risposta

3

esiste già, e si chiama guardia ambito. Vedi questo fantastico discorso: https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C. Ciò ti consente di creare facilmente un chiamabile arbitrario da chiamare all'uscita. Questa è la versione più recente; è stato sviluppato in origine molto tempo prima che esistesse.

Funziona perfettamente in generale, ma non sono sicuro di cosa intendiate gestendo le eccezioni. Lanciare le eccezioni da una funzione che deve essere richiamata all'uscita dallo scope è un casino. Il motivo: quando un'eccezione viene lanciata (e non immediatamente rilevata), l'attuale portata viene chiusa. Tutti i distruttori vengono eseguiti e l'eccezione continuerà a propagarsi. Se uno dei distruttori lancia, cosa fai? Ora hai due eccezioni live.

Suppongo che ci siano modi in cui un linguaggio potrebbe provare ad affrontarlo, ma è molto complesso. In C++, è molto raro che un distruttore di lancio sia considerato una buona idea.

+0

Intendevo dire che funziona correttamente quando un'eccezione viene lanciata a metà funzione, non nel callback. Scoprire ScopeGuard è molto utile. Grazie! – daveagp

-1

Questo esiste già in C++ ed è un'idea tremendamente cattiva e l'esempio che hai fornito esemplifica perché è una cosa inutile da fare e spero che il Comitato non lo introduca mai.

Ad esempio, se si dispone di un handle di file, quindi scrivere una classe per farlo per voi e quindi non sarà necessario scrivere una dichiarazione di rinvio per ogni singolo caso d'uso, che si potrebbe facilmente dimenticare di fare. O semplicemente sbagliato. Scrivi un distruttore, una volta. Questo è tutto. Allora sei garantito per tutti gli usi della classe che è sicuro. È molto più sicuro e molto più facile.

7

Boost discutere di questo in puntatore intelligente Tecniche di programmazione:

si può fare, per esempio:

#include <memory> 
#include <iostream> 
#include <functional> 

using namespace std; 
using defer = shared_ptr<void>;  

int main() { 
    defer _(nullptr, bind([]{ cout << ", World!"; })); 
    cout << "Hello"; 
} 

Oppure, senza bind:

#include <memory> 
#include <iostream> 

using namespace std; 
using defer = shared_ptr<void>;  

int main() { 
    defer _(nullptr, [](...){ cout << ", World!"; }); 
    cout << "Hello"; 
} 

Forse potresti pure rollout vostra propria piccola classe per tali, o fare uso della implementazione di riferimento per N3830/P0052:

The C++ Core Guidelines anche have a guideline che utilizza la funzione gsl::finally, per la quale esiste un'implementazione here.

Esistono molte codebase che impiegano soluzioni simili per questo, quindi, c'è una richiesta per questo strumento.

SO correlate discussione: