Il caso di C ++
Nei costruttori C ++ ci si aspetta che abbiano successo. L'unico modo per il fallimento di un costruttore è di generare un'eccezione.
Bjarne Stroustrup conferma nel suo libro "The design and evolution of C ++" che questo è uno degli aspetti più importanti delle eccezioni (Capitolo 16.5.1: Errori nei costruttori ), come hai giustamente sottolineato fuori nella tua domanda.
Ora immagina per un secondo che un costruttore possa fallire senza eccezioni ...
L'inizializzazione del puntatore Object *o = new Object;
potrebbe funzionare con questa semantica senza problemi con un nullptr
. In effetti, è possibile ottenere questa semantica se l'errore è causato da un problema di allocazione con nothrow
.
Ma come potresti gestire i seguenti casi:
- Costruzione dell'oggetto locale (valore):
Object o;
richiama ad esempio un costruttore predefinito. Se la costruzione fallita fosse possibile, tu o il compilatore dovresti verificare la presenza di un oggetto vuoto in tutti gli usi successivi di o
. Nella maggior parte dei casi questo sarebbe un overhead delle prestazioni che è giustificato solo da alcuni casi eccezionali, che non è la filosofia progettuale di C ++
- Costruzione di un membro della classe: ad esempio
class X { public: Object o; };
. Se la costruzione di o
fallirebbe, in questo semplice esempio si potrebbe semplicemente immaginare di dire che anche la costruzione dell'oggetto class X
ha esito negativo. Ma come dovrebbe funzionare se la classe X avesse diversi membri diversi, alcuni con una costruzione di successo e alcuni con un oggetto fallito? E se ci fossero altri membri del puntatore in cui il compilatore non può determinare se l'oggetto puntato debba essere distrutto o meno in caso di una costruzione parzialmente fallita di X
?
- Costruzione di una classe derivata: cosa fare se un oggetto classe base può essere costruito con successo, ma l'oggetto classe derivata no: l'oggetto classe bbase deve essere restituito? O dovrebbe essere distrutto?
- Non menziono ereditarietà multipla o eredità virtuale che hanno problemi simili.
Se C ++ consentirebbe ai costruttori di fallire, ci sarebbero molti problemi semantici irrisolti, o un sovraccarico che è inutile la maggior parte delle volte. Questo spiega l'attuale opzione di progettazione: gestire eccezionalmente circostanze eccezionali.
A proposito, Object o = new Object();
non è valido in C ++.
Problema di progettazione del codice?
Se ritieni di avere la necessità di un costruttore che si suppone fallisca come comportamento normale, c'è sicuramente un problema di progettazione.
Potrebbe essere quindi necessario prendere in considerazione l'utilizzo di un oggetto con uno stato speciale fail
. Considereresti nuovamente che la costruzione ha sempre successo, ma che lo stato dell'oggetto potrebbe non essere quello previsto. Puoi quindi controllare ogni volta che è necessario se lo stato dell'oggetto è ok o meno.
casi Java e C #
Anche Java e C # si aspettano che il costruttore abbia successo o che generi un'eccezione. Le ragioni sono molto simili a quelle menzionate per C ++. Sebbene il caso 1 non sia applicabile perché gli oggetti sono gestiti per riferimento e il caso 4 non è rilevante perché questi linguaggi non supportano l'MI, i casi 2 e 3 causerebbero problemi semantici in caso di oggetti parzialmente creati.
Se il linguaggio consentiva il fallimento del costruttore senza eccezioni, avrebbe dovuto decidere come gestire una costruzione parzialmente riuscita (mantenere gli oggetti membro corretti e lasciare che l'altro fosse nullo o distruggere i membri costruiti e restituire nil). Ma la semantica scelta potrebbe non essere quella corretta in ogni caso. La gestione delle eccezioni consente una maggiore flessibilità in questo senso.
Come soluzione all'odore del progetto, oltre allo stato di errore speciale già citato per C ++, potresti anche facilmente utilizzare una fabbrica o un costruttore che potrebbe restituire zero, ad esempio se i parametri di costruzione non sono validi.