Che cosa guadagnano i linguaggi OOP dall'avere costruttori che restituiscono sempre un oggetto?

2

In quella che sembra una deliberata decisione progettuale, il C ++ non ha un valore nullo per oggetti e riferimenti. Questo rende l'uso di oggetti e riferimenti molto elegante poiché non è necessario eseguire controlli nulli. Significa anche che i costruttori devono sempre restituire un oggetto:

void main () {
    // o is guaranteed to be non-null by c++:
    Object o = new Object();         
}

// Allowing this hypothetical "failing" constructor would break the guarantee:
Object() { return void }

A volte la creazione dell'oggetto deve fallire, un semplice esempio è un oggetto Connection . Poiché il C ++ non lo consente, i programmatori hanno sviluppato alternative: metodi di inizializzazione (con vari nomi), metodi di build statici, "Fabbriche" che non usano alcun polimorfismo e oggetti nulli. Un motivo per aggiungere eccezioni potrebbe essere stato un modo per aggirare questa limitazione dei costruttori C ++ , ma in entrambi i casi, è un compromesso: evitiamo i controlli nulli ma a volte abbiamo una costruzione scomoda.

Questo spiega perché C ++ non consente ai costruttori di restituire null. Ma è insufficiente quando consideriamo i nuovi linguaggi OOP come Java e C #. Lì, i riferimenti possono essere nulli, tuttavia i costruttori funzionano come C ++ e non devono fallire. Quindi scriviamo sia controlli nulli sia schemi costruttivi alternativi (dovendo scrivere fabbriche, metodi init, ecc.). Quali vantaggi offre?

    
posta Fadeway 14.08.2018 - 18:34
fonte

4 risposte

6

Il metodo standard di gestione di un costruttore che fallisce è di generare un'eccezione. I costruttori di solito non falliscono, e di solito è più facile gestire tali errori in un posto separato piuttosto che ingombrare la logica principale.

Se un costruttore di Connection fallisce, qualunque cosa dovesse accadere con quella Connection non sta per accadere, quindi lanciare piuttosto che eseguire il resto della funzione o provare ... catch block è appropriato.

Se vuoi un oggetto che potrebbe o potrebbe non esistere, considera std :: optional (o boost :: opzionale se il tuo compilatore C ++ è più vecchio).

    
risposta data 14.08.2018 - 22:13
fonte
7

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:

  1. 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 ++
  2. 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 ?
  3. 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?
  4. 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.

    
risposta data 14.08.2018 - 23:55
fonte
3

Il tuo esempio con C ++ non funzionerà perché C ++ ha oggetti stack oltre ai puntatori.

int main
{ 
  Object x;
  x = nullptr; // x is not a pointer, this makes no sense.
}

Inoltre, void non è un valore, quindi non puoi restituire o assegnare void. L'esistenza stessa delle variabili stack rende il concetto di assurdità di un costruttore che restituisce un vuoto. In un'altra lingua che non consentiva gli oggetti stack, puoi immaginare di farlo:

Class OPQ
{
  Constructor(bool b) { if (b == false) return null; else return this; }
}

e quindi forse la garbage collection ripulirebbe il tuo OPQ quando il costruttore uscirà. Ciò significa che il chiamante dovrà gestire risultati nulli, ma d'altra parte questo significa che una tale costruzione funzionerebbe in un linguaggio di programmazione senza eccezioni. (forse uno dove le operazioni con oggetti nulli sono no-op.)

Quindi penso che la linea di fondo, non c'è costrizione in entrambi i casi in generale. Le variabili automatiche del C ++ precludono la nozione, ma un linguaggio basato sul riferimento può fare come vuole. Probabilmente C # e Java hanno scelto come hanno fatto perché i produttori di linguaggi amavano le eccezioni e volevano imitare C ++ in una certa misura.

    
risposta data 14.08.2018 - 20:41
fonte
1

Swift non ha alcun problema con questo. Le istanze delle classi sono oggetti di riferimento, è possibile avere classi opzionali, quindi è possibile avere inizializzatori che non possono fallire, o inizializzatori f disponibili. L'implementazione di un inizializzatore disponibile è in qualche modo complicato poiché il linguaggio deve ripulire correttamente qualsiasi oggetto inizializzato a metà, ma ciò è invisibile al programmatore.

La cosa buona in Swift è che non puoi semplicemente ignorare il fatto che un oggetto facoltativo potrebbe essere zero.

    
risposta data 15.08.2018 - 20:07
fonte

Leggi altre domande sui tag