Transizione a C ++ 11 dove i distruttori sono implicitamente dichiarati senza eccezioni

In C ++ 11, un distruttore senza alcuna specifica di eccezione viene implicitamente dichiarato con noexcept , che è una modifica da C ++ 03. Pertanto, un codice che usava lanciare dai distruttori in C ++ 03 sarebbe comunque ben compilato in C ++ 11, ma si arresterebbe in fase di runtime quando tenterà di lanciare da un tale distruttore.

Dal momento che non esiste un errore in fase di compilazione con un tale codice, come potrebbe essere tranquillamente passato a C ++ 11, a meno di dichiarare tutti i distruttori esistenti nella base di codice come non noexcept(false) , che sarebbe davvero troppo prolisso e invadente, o ispezionando ogni singolo distruttore per il potenziale lancio, il che richiederebbe molto tempo ed è sobject a errori, o catturare e correggere tutti gli arresti anomali in fase di runtime, cosa che non garantirebbe mai la ricerca di tutti questi casi?

Nota che le regole non sono in realtà così brutali. Il distruttore sarà implicitamente noexcept se un distruttore implicitamente dichiarato sarebbe. Pertanto, contrassegnando almeno una class di base o un tipo di membro come noexcept (false) avvelenerà la noexcept dell’intera gerarchia / aggregato.

 #include  struct bad_guy { ~bad_guy() noexcept(false) { throw -1; } }; static_assert(!std::is_nothrow_destructible::value, "It was declared like that"); struct composing { bad_guy member; }; static_assert(!std::is_nothrow_destructible::value, "The implicity declared d'tor is not noexcept if a member's" " d'tor is not"); struct inheriting : bad_guy { ~inheriting() { } }; static_assert(!std::is_nothrow_destructible::value, "The d'tor is not implicitly noexcept if an implicitly" " declared d'tor wouldn't be. An implicitly declared d'tor" " is not noexcept if a base d'tor is not."); struct problematic { ~problematic() { bad_guy {}; } }; static_assert(std::is_nothrow_destructible::value, "This is the (only) case you'll have to look for."); 

Tuttavia, sono d’accordo con Chris Beck sul fatto che prima o poi dovresti sbarazzarti dei tuoi distruttori di lancio. Possono anche far esplodere il programma C ++ 98 nei momentjs più scomodi.

Come 5gon12eder ha menzionato, ci sono alcune regole che portano a un distruttore senza una specifica di eccezione da dichiarare implicitamente come noexcept o noexcept(false) . Se il tuo distruttore può lanciare e lasciare che il compilatore decida la sua specifica di eccezione, giocherebbe una roulette, perché ti affidi alla decisione del compilatore influenzata dagli antenati e dai membri della class, dai loro antenati e membri in modo ricorsivo , che è troppo complesso da tracciare ed è sobject a modifiche durante l’evoluzione del tuo codice. Pertanto, quando si definisce un distruttore con un corpo che può essere lanciato e nessuna specifica di eccezione, deve essere dichiarato esplicitamente come noexcept(false) . D’altra parte, se sei sicuro che il corpo non può lanciare, puoi dichiararlo non più esplicito e aiutare il compilatore a ottimizzare, ma fai attenzione se scegli di farlo, perché se un distruttore di un membro / l’antenato della tua class decide di lanciare, il tuo codice si interromperà in fase di runtime.

Nota che qualsiasi distruttore o distruttore implicitamente definito con corpi vuoti non pone problemi. Sono implicitamente solo noexcept se tutti i distruttori di tutti i membri e gli antenati non lo sono.

Il modo migliore per procedere con la transizione è quindi quello di trovare tutti i distruttori con corpi non vuoti e nessuna specifica di eccezione e dichiarare ognuno di essi che può lanciare senza noexcept(false) . Nota che devi solo controllare il corpo del distruttore – qualsiasi tiro immediato o qualsiasi tiro fatto dalle funzioni che chiama, in modo ricorsivo. Non è necessario controllare i distruttori con corpi vuoti, distruttori con una specifica di eccezione esistente o qualsiasi distruttore implicitamente definito. In pratica, non ci sarebbero molti di quelli che devono essere controllati, in quanto l’uso prevalente di questi è semplicemente la liberazione delle risorse.

Dato che sto rispondendo a me stesso, è esattamente quello che ho fatto nel mio caso e non è stato poi così doloroso.

Ho passato questo stesso dilemma una volta.

Fondamentalmente ciò che ho concluso è che, accettando il fatto che quei distruttori stiano lanciando e semplicemente vivendo con le conseguenze di ciò, di solito è molto peggio che passare attraverso il dolore di non farli gettare.

La ragione è che si rischiano stati ancora più instabili e imprevedibili quando si lanciano distruttori.

Ad esempio, ho lavorato a un progetto una volta in cui, per vari motivi, alcuni sviluppatori utilizzavano eccezioni per il controllo del stream in alcune parti del progetto e funzionava perfettamente per anni. Più tardi, qualcuno ha notato che in una parte diversa del progetto, a volte il client non riusciva a inviare alcuni messaggi di rete che doveva inviare, così hanno creato un object RAII che avrebbe inviato i messaggi nel suo distruttore. A volte il networking generava un’eccezione, quindi questo distruttore RAII avrebbe lanciato, ma chi se ne frega? Non ha memoria da ripulire, quindi non è una perdita.

E ciò funzionerebbe bene per il 99% delle volte, tranne quando il percorso di controllo del stream delle eccezioni si verificava per attraversare il networking, che quindi genera anche un’eccezione. E poi, hai due eccezioni live che vengono svolte in una sola volta, quindi “bang sei morto”, nelle parole immortali delle FAQ C ++.

Onestamente preferirei che il programma si interrompesse istantaneamente quando un distruttore getta, quindi possiamo parlare con chi ha scritto il distruttore di lancio, piuttosto che provare a mantenere un programma con intenzionalmente a lanciare i distruttori, e questo è il consenso della commissione / comunità che sembra . Così hanno fatto questo cambiamento decisivo per aiutarti ad affermare che i tuoi distruttori sono bravi e non lanciano. Potrebbe essere molto lavoro se il tuo codice base ha molti hack, ma se vuoi continuare a svilupparlo e mantenerlo, almeno sullo standard C ++ 11, è meglio fare il lavoro di pulizia su i distruttori.

Linea di fondo:

Hai ragione, non puoi davvero sperare di garantire di trovare tutte le possibili istanze di un distruttore di lancio. Quindi ci saranno probabilmente degli scenari in cui, quando il tuo codice viene compilato in C ++ 11, si bloccherà nei casi in cui non sarebbe conforms allo standard C ++ 98. Ma nel complesso, ripulire i distruttori e correre come C ++ 11 sarà probabilmente molto più stabile che andare con i distruttori di lancio sui vecchi standard.