2016-04-02 15 views
6

Let dire che ho classe RAII:C++ utilizzando RAII con distruttore che lancia

class Raii { 
    Raii() {}; 
    ~Raii() { 
     if (<something>) throw std::exception(); 
    } 
}; 

E se ho la funzione:

void foo() { 
    Raii raii;  

    if (something) { 
     throw std::exception(); 
    } 
} 

Questo è un male, perché durante la pulizia per la prima eccezione che possiamo lanciare di nuovo e questo interromperà il processo.

La mia domanda è: qual è uno schema valido per utilizzare raii per il codice che la pulizia potrebbe generare?

Ad esempio, questo è buono o cattivo: perché?

class Raii { 
    Raii() {}; 
    ~Raii() { 
     try { 
      if (<something>) throw std::exception(); 
     } 
     catch (...) { 
      if (!std::uncaught_exception()) 
       throw; 
     } 
    } 
}; 

Si noti che l'oggetto Raii è sempre un oggetto assegnato allo stack e questo non è il problema generale del problema del distruttore.

+8

Questo non è affatto diverso dal caso più generale di "come impedire la chiusura se ho una classe il cui distruttore può lanciare". Non penso che ci sia una buona risposta se non "i distruttori dovrebbero * non * lanciare". – sfjac

+1

Senza il distruttore che fa del lavoro non c'è RAII. Spero davvero che ci sia qualcosa che potrebbe essere fatto. Dì qualcosa basato su std :: uncaught_exception dove puoi rilevare se c'è un'eccezione in corso, per esempio. – gsf

risposta

7

C++ avrà quasi certamente una funzione per ottenere l'attuale conteggio delle eccezioni a partire da C++ 1z (ovvero C++ 17 se lo pubblicano in tempo!): std::uncaught_exceptions (nota il plurale "s"). Inoltre, i distruttori sono dichiarati come noexcept per impostazione predefinita (nel senso che se si tenta di uscire da un distruttore tramite un'eccezione, viene chiamato std::terminate).

Quindi, prima, contrassegnare il distruttore come un lancio (noexcept(false)). Successivamente, traccia il numero di eccezioni attive in ctor, confrontalo con il valore in dtor: se ci sono più eccezioni non catturate nel dtor, sai che sei attualmente nel processo di stack unwinding, e il lancio di nuovo comporterà una chiamata a std::terminate.

Ora tu decidi esattamente quanto sei eccezionale e come desideri gestire la situazione: termina il programma o semplicemente deglutisci l'eccezione interna?

Una copia sbiadita è quello di non gettare se uncaught_exception (singolare) restituisce vero, ma che rende le eccezioni non funzionano quando viene chiamato da un diverso dtor del innescato da srotolando che sta cercando di catturare ed elaborare vostra eccezione. Questa opzione è disponibile negli attuali standard C++.

+0

@kerrek è stato finalizzato al C++ 17? – Yakk

+0

Il documento pertinente è stato votato e applicato (N4582), e non c'è praticamente nessuna possibilità che venga eliminato, ora che il WD è considerato "completo di funzionalità" per 17. –

4

Il consiglio da the ScopeGuard article era

Nel regno delle eccezioni, è fondamentale che si può fare nulla se il tuo "undo/recuperare" l'azione fallisce. Si tenta un'operazione di annullamento e si procede indipendentemente dal fatto che l'operazione di annullamento abbia esito positivo o meno.

Può sembrare pazzesco, ma prendere in considerazione:

  1. riesco a corto di memoria e di ottenere un std::bad_alloc un'eccezione
  2. Il mio codice di clean-up registra l'errore
  3. Purtroppo, la scrittura fallisce (forse il disco è pieno) e tenta di generare un'eccezione

Posso annullare la scrittura del registro? Dovrei provare?

Quando viene emessa un'eccezione, tutto ciò che si sa è che il programma non è valido. Non dovresti essere sorpreso dal fatto che alcune cose impossibili risultino possibili dopo tutto.Personalmente, ho visto molti più casi in cui il consiglio di Alexandrescu ha più senso che altro: provare a ripulire, ma riconoscere che la prima eccezione significa che le cose sono già in uno stato non valido, quindi ulteriori errori - specialmente i guasti causati dal primo problema ("errore a cascata") - non dovrebbe essere una sorpresa. E provare a gestirli non finirà bene.


Probabilmente dovrei menzionare che capitano Proto fa esattamente quello che hai proposto:

Quando il codice Capitano Proto potrebbe generare un'eccezione da un distruttore, si verifica innanzitutto std::uncaught_exception() per garantire che questo è sicuro. Se un'altra eccezione è già attiva, si presume che la nuova eccezione sia un effetto collaterale dell'eccezione principale ed è inghiottita o segnalata in silenzio su un canale laterale.

Ma, come diceva Yakk, i distruttori sono diventati nothrow(true) di default in C++ 11. Il che significa che se vuoi farlo, devi essere sicuro che in C++ 11 e in seguito contrassegni il distruttore come nothrow(false). Altrimenti, lanciando un'eccezione dal distruttore terminerà il programma anche se non ci sono altre eccezioni in volo. E nota: "Se un'altra eccezione è già attiva, si presume che la nuova eccezione sia un effetto collaterale dell'eccezione principale e sia inghiottita in silenzio o segnalata su un canale laterale."