Quindi la mia domanda è questa: se il lancio da un distruttore provoca un comportamento indefinito, come gestite gli errori che si verificano durante un distruttore?
Il problema principale è questo: non puoi fallire . Cosa significa non fallire, dopo tutto? Se il commit di una transazione in un database fallisce e non riesce (fallisce il rollback), cosa succede all'integrità dei nostri dati?
Poiché i distruttori sono invocati per percorsi normali ed eccezionali (falliti), essi stessi non possono fallire, altrimenti stiamo "fallendo".
Questo è un problema concettualmente difficile ma spesso la soluzione è trovare un modo per assicurarsi che il fallimento non possa fallire. Ad esempio, un database potrebbe scrivere le modifiche prima di eseguire il commit in una struttura di dati o file esterni. Se la transazione fallisce, è possibile eliminare la struttura di file / dati. Tutto ciò che deve quindi garantire è che il commit delle modifiche da quella struttura / file esterno sia una transazione atomica che non può fallire.
La soluzione pragmatica è forse solo quella di assicurarsi che le possibilità di fallire in caso di fallimento siano astronomicamente improbabili, dal momento che rendere impossibili fallire le cose può essere quasi impossibile in alcuni casi.
La soluzione più corretta per me è scrivere la tua logica di non cleanup in modo tale che la logica di cleanup non possa fallire. Ad esempio, se sei tentato di creare una nuova struttura di dati per ripulire una struttura di dati esistente, forse potresti cercare di creare in anticipo quella struttura ausiliaria in modo da non dover più crearla all'interno di un distruttore.
Questo è tutto molto più facile a dirsi che a farsi, devo ammetterlo, ma è l'unico modo davvero corretto che vedo per farlo. A volte penso che dovrebbe esserci la possibilità di scrivere una logica di distruttore separata per percorsi di esecuzione normale lontano da quelli eccezionali, dal momento che a volte i distruttori si sentono un po 'come se avessero il doppio delle responsabilità cercando di gestire entrambi (un esempio sono le guardie dell'ambito che richiedono un licenziamento esplicito non lo richiederebbero se potessero differenziare percorsi di distruzione eccezionali da percorsi non eccezionali).
Il problema finale è che non possiamo non fallire, ed è un problema di progettazione concettuale difficile da risolvere perfettamente in tutti i casi. Diventa più facile se non ti impigli troppo in complesse strutture di controllo con tonnellate di oggetti adolescenti che interagiscono tra loro, e invece modella i tuoi progetti in modo leggermente più voluminoso (esempio: sistema di particelle con un distruttore per distruggere l'intera particella sistema, non un distruttore non banale separato per particella). Quando modellate i vostri progetti a questo tipo di livello più grossolano, avete meno distruttori non banali da gestire e spesso potete anche permettervi qualsiasi memoria / elaborazione ambientale necessaria per assicurare che i vostri distruttori non possano fallire.
E questa è una delle soluzioni più semplici naturalmente è quella di utilizzare i distruttori meno spesso. Nell'esempio di particella sopra, forse dopo aver distrutto / rimosso una particella, si dovrebbero fare alcune cose che potrebbero fallire per qualsiasi motivo. In tal caso, invece di invocare tale logica attraverso il dtor della particella che potrebbe essere eseguita in un percorso eccezionale, si potrebbe invece fare tutto dal sistema particellare quando rimuove una particella. La rimozione di una particella potrebbe sempre essere eseguita durante un percorso non eccezionale. Se il sistema viene distrutto, forse può semplicemente eliminare tutte le particelle e non disturbare con quella logica di rimozione delle singole particelle che può fallire, mentre la logica che può fallire viene eseguita solo durante la normale esecuzione del sistema di particelle quando rimuove una o più particelle.
Esistono spesso soluzioni simili a quelle che emergono se si evita di occuparsi di molti oggetti adolescenti con distruttori non banali. Dove puoi rimanere impigliato in un pasticcio in cui sembra quasi impossibile essere eccezionalmente sicuro, è quando rimani impigliato in molti oggetti per adolescenti che hanno tutti dei dottori non banali.
Sarebbe molto utile se nothrow / noexcept fosse effettivamente tradotto in un errore del compilatore se qualcosa che lo specifica (incluse le funzioni virtuali che dovrebbero ereditare la specifica noexcept della sua classe base) ha tentato di invocare qualsiasi cosa che potesse essere lanciata. In questo modo saremmo in grado di catturare tutta questa roba in fase di compilazione se in realtà scrivessimo inavvertitamente un distruttore che potrebbe lanciare.