Perché il bool di restituzione del costruttore non indica il suo esito positivo o negativo senza dover generare un'eccezione?

6

Prendi i costruttori C ++ per esempio, sappiamo che non restituiscono valori. Perché all'inizio Bjarne Stroustrup ha deciso non di consentire al costruttore di restituire 'falso' per indicare che fallisce, in modo che il sistema di runtime possa distruggere i membri costruiti proprio come lanciare un'eccezione? Quali sono le preoccupazioni che rendono i progettisti di linguaggi OO decidono non di farlo? L'operatore "nuovo" può quindi restituire "nullptr" se vede che il costruttore restituisce false. Per gli oggetti statici (nell'area .data / .bss o su .stack) il codice di costruzione generato dal compilatore può ancora rilevare e segnalare, interrompere o uscire di conseguenza.

Considerare i seguenti due paradigmi di codifica, utilizzando l'allocazione dinamica come esempio:

objp = new object; // constructor returns 'false', 'new' returns 'nullptr'.
if (objp != nullptr) {
    do_something(objp);
} else {
    error_handling();
}

confronta con:

try {
    objp = new object; // object throw exception when construction failed
    do_something(objp);
} catch (const typedException &e) {
    error_handling();
}

Se abbiamo bisogno di codificare il motivo del fallimento della costruzione, quest'ultimo usando l'eccezione è ovviamente più utile in quanto non possiamo usare 'objp' per passare alcuna informazione una volta che la costruzione fallisce. Ma se il motivo è semplice (diciamo 'out of memory'), o quando saltare la gestione degli errori non fa male (do_something () potrebbe semplicemente fare la decorazione), abbiamo davvero bisogno di coinvolgere la gestione delle eccezioni in casi così semplici? Che ne dici di consentire l'esistenza di entrambi i paradigmi in C ++?

(Un altro esempio è che per i piccoli compilatori C ++ integrati, se non supportano le eccezioni, possono ancora supportare la gestione della "costruzione fallita" in questo modo.)

Bene, forse il mio paragone è fuorviante, non sono contrario alla gestione delle eccezioni strutturali, al contrario, le eccezioni di LOVE specialmente per i grandi sistemi. Ma quando si tratta di piccoli sistemi embedded in cui sia il codice che lo spazio dati sono scarsi, ci penso due volte.

I frame di gestione delle eccezioni sono simili a setjmp () e longjmp (), che sono piuttosto costosi sia in termini di tempo di esecuzione che di utilizzo della memoria; mentre il confronto (objp == nullptr) richiede solo un confronto e un salto. Per non parlare di alcuni dei compilatori non supportano nemmeno la gestione delle eccezioni. In questi casi, il fallimento della costruzione può essere affrontato solo con altri metodi. Ciò mi ricorda ai vecchi tempi che il Turbo Pascal 5.5 OOP può chiamare "Fail" in caso di errore di costruzione, e l'oggetto appena assegnato sarà nullo.

E le altre lingue? Tutte le lingue OOP utilizzano l'eccezione sui casi di errore di costruzione?

In realtà, nel momento in cui ho imparato C ++ non è disponibile alcuna gestione delle eccezioni (Turbo C ++ 3.0 / Borland C ++ 3.1). Prima di allora ho imparato Turbo Pascal 5.5 che supportava "Fail" in caso di failover dinamico della costruzione. Ma quando mi sposto in C ++ in quel momento questo mi rende un po 'turbato dal momento che non c'è modo di testare i fallimenti di costruzione senza definire una funzione Init () che in realtà faccia gli inits. Da allora mi sono chiesto, perché i costruttori non possono restituire valori?

Ora penso che forse restituire un valore dal costruttore renderà la lingua "incoerente". Se restituiamo un valore da un costruttore e il sistema runtime lo test, questo tipo di comportamento è molto diverso da una normale chiamata di funzione poiché il nostro codice può testarlo. Forse questo tipo di incoerenza non è una buona idea quando si progetta un linguaggio di programmazione? Cosa ne pensi?

All'inizio del C ++ non esisteva alcuna gestione delle eccezioni, tuttavia Bjarne Stroustrup non permetteva ancora ai costruttori di restituire condizioni di errore. Lo stesso ha fatto le altre lingue OO in quel momento (correggimi se sbaglio). Pertanto, usando la gestione delle eccezioni non era la loro intenzione originale di occuparsi del costruttore fallisce. Allora perché? Penso di aver trovato la risposta, per favore fai riferimento a la mia risposta qui . Grazie.

    
posta Community 18.10.2016 - 06:24
fonte

7 risposte

13

La tua domanda indica una fondamentale incomprensione del valore del meccanismo di eccezione. Se la lingua in questione non ha eccezioni, ovviamente i codici di errore sarebbero l'unico modo per andare. Ma usare i codici di errore in una lingua che supporta le eccezioni non è spesso la risposta giusta.

Perché?

Come risulta, molto spesso, il codice molto vicino alla fonte di un problema di interruzione dello show non sa cosa fare quando ciò accade.

Dato che il codice vicino all'errore spesso non sa cosa fare, non c'è nulla da fare per quel codice per gestire l'errore, tranne consentire a qualcun altro di farlo.

Con il ritorno del codice di errore, ogni chiamante deve controllare gli errori, anche se di solito non c'è nulla che possono fare, ma per propagare ulteriormente l'errore ai chiamanti (schiuma, risciacquo, ripetizione ...)

Inserisci gestione delle eccezioni

La gestione delle eccezioni è un meccanismo che consente una separazione dei calle che hanno problemi da gestori di errori veramente capaci, che sono in genere piuttosto rimossi dalla fonte di un errore.

I buoni gestori sanno come interrompere o riprovare il calcolo più ampio che viene eseguito, non semplicemente registrare l'errore e propagare l'errore nella catena di chiamate. Un segno di un buon gestore è che non ha bisogno di propagare gli errori nella catena di chiamata perché li ha gestiti .

Con la gestione delle eccezioni, in genere non circondiamo le singole chiamate di metodo con la gestione delle eccezioni, e questo include costruttori e nuove operazioni .

La gestione delle eccezioni consente una migliore definizione di un gestore di eccezioni e ad una distanza considerevole dall'origine dell'eccezione, e questo è un vantaggio perché tale distanza di solito significa una migliore comprensione di cosa fare in caso di errori.

(La gestione delle eccezioni consente anche errori di strutturazione, ma lasceremo tutto ciò per un'altra discussione.)

Se non ti abbiamo ancora convinto, confronta queste sequenze di codice:

objp = new object (); // constructor returns 'false', 'new' returns 'nullptr'.
if (objp != nullptr) {
    do_something(objp);
} else {
    error_handling();
}

con:

objp = new object (); // object throw exception when construction failed

Quest'ultimo, utilizzando le eccezioni, è molto più pulito e più appropriato per una maggioranza discutibile di casi d'uso.

    
risposta data 18.10.2016 - 06:44
fonte
5

Certamente potrebbe definire un linguaggio che ha funzionato in questo modo, ma finirebbe per essere una lingua piuttosto diversa rispetto al C ++.

Ad esempio, in C ++ un costruttore può essere invocato per creare un oggetto temporaneo nel processo di valutazione di un'espressione:

MyInteger a = 2;
MyInteger b = 3;
MyInteger c;
c = a + b;

Quando lo facciamo, a + b crea un oggetto temporaneo (presumibilmente di tipo MyInteger ). L'oggetto viene quindi assegnato a c .

Se, tuttavia, il costruttore ha restituito un risultato booleano, questo non funzionerebbe altrettanto bene. a+b avrebbe (apparentemente) risultato in true o false . Quelli convertono rispettivamente in 1 e 0 , quindi il risultato di 2 + 3 sarebbe 0 o 1 (allo stesso modo, tutti gli altri matematici darebbero risultati di 0 o 1).

Ci sono modi in cui puoi ovviare a questo problema, ovviamente. Ad esempio, puoi semplicemente vietare l'assegnazione di oggetti. Con questa restrizione, potresti scrivere qualcosa come:

MyInteger a{2};
MyInteger b{3};
MyInteger c{a};
c.add(b);

Finché stiamo aggiungendo solo due numeri, questo non è un grosso problema. In espressioni più complesse, tuttavia, mi sembra che il risultato sarebbe innaturale e quasi insopportabile verboso. Il programmatore sarebbe costretto a creare esplicitamente ogni oggetto temporaneo, inizializzato da uno degli operandi di input, e quindi a modificarlo in base all'altro operando. Ad esempio a = (b+c)*(d/e); finirebbe per qualcosa come:

MyInteger temp1{b};
temp1.add(c);
MyInteger temp2{d};
temp2.div(e);
temp1.mul(temp2);
MyInteger a{temp1};

È certamente possibile fare cose del genere. In effetti, esiste un linguaggio che ha fornito funzionalità simili (usando sintassi simile) per anni:

 mov ebx, b
 add ebx, c
 mov eax, d
 xor edx, edx
 mov ecx, e
 div ecx
 mul eax, ebx

L'intento con C ++ era di passare a un livello più alto di astrazione. Questo linguaggio che hai inventato va nella direzione opposta, reinventando il linguaggio assembly, ma con una sintassi più dettagliata.

Sommario

Ciò che stai sostenendo potrebbe essere fatto sicuramente. Sarebbe abbastanza facile da progettare e implementare. Il risultato sarebbe così maldestro che io (per uno) trovo difficile immaginare che comunque qualcuno lo userebbe.

    
risposta data 18.10.2016 - 17:44
fonte
2

Entrambi gli approcci (eccezioni o codici di errore) sono già supportati in queste lingue. Per usare l'approccio del codice di errore, non lanciare mai eccezioni o chiamare qualsiasi cosa che possa generare. Scrivi costruttori che non fanno altro che copiare valori puri in campi. Sposta tutte le operazioni potrebbero fallire in metodi separati.

Nei tempi andati male, Microsoft raccomandava effettivamente questo approccio (probabilmente perché il loro precoce compilatore C ++ non implementava affatto lo sdoppiamento dello stack di eccezioni).

La sfida con questo è che il "puro approccio al codice di errore" è spesso impossibile perché quasi tutto ciò che fai in uno qualsiasi dei tuoi codici ha un potenziale di lancio. Tutto ciò che alloca la memoria, ad esempio, che include quasi tutte le operazioni su una stringa.

Quindi la scelta è davvero tra:

  • che si occupa di eccezioni e controlla anche i codici di errore restituiti
  • si occupano solo di eccezioni

Se devi comunque fare i conti con le eccezioni, potresti anche usarli in modo coerente.

L'argomento del vincolo di risorse viene preso seriamente in considerazione da alcune comunità, ad es. ambienti embedded molto limitati. Ma questa è una piccola nicchia. I telefoni erano limitati in termini di risorse, ora la maggior parte dei software telefonici è basata su Java utilizzando GC, eccezioni ecc.

    
risposta data 18.10.2016 - 09:37
fonte
0

The exception handling frames are things similar to setjmp() and longjmp() which are quite expensive in both execution time and memory use; while the (objp==nullptr) comparison takes only a comparison and a jump

Sono un confronto e un salto in ogni caso, incluso il caso di successo. EH può essere a costo zero di un'eccezione non generata, quindi può offrire un tempo migliore nel normale caso di successo.

Come ha giustamente sottolineato Erik, l'intero punto delle eccezioni è che non controlla eccezioni ovunque.

    
risposta data 18.10.2016 - 09:29
fonte
0

Puoi farlo già senza modifiche di lingua.

Aggiungi un bool & restituisce un parametro a ciascun costruttore e lo imposta invece di generare eccezioni.

Puoi allocare oggetti heap con nothrow new

Ma onestamente potresti quasi scrivere a questo punto.

    
risposta data 19.10.2016 - 17:56
fonte
0

Succede (tipo di) in Objective-C. Gli oggetti appena creati sono sempre nell'heap, quindi il metodo init (un po 'come il costruttore in C ++) restituisce un riferimento all'oggetto. Se il metodo init fallisce, dovrebbe restituire zero.

Puoi annotare un metodo init come "nonnull", il che significa che non è consentito restituire nil; in questo caso si presume che il metodo init non fallisca mai se gli vengono dati i parametri corretti e che i parametri errati sono un errore del programmatore, quindi se il metodo init ha voluto restituire nulla allora sarebbe un errore di programmazione e dovrebbe essere sollevata un'eccezione - Nell'Obiettivo C, le eccezioni uccidono il programma nella maggior parte delle situazioni (vengono sollevate per errori di programmazione, quindi raramente provate / catturandole).

Naturalmente restituire nil o non nil è buono come un parametro booleano. Questo è ciò che possono fare anche i metodi di produzione in C ++; li chiami e ottieni un puntatore a un oggetto appena creato o ottieni un puntatore nullo.

    
risposta data 21.10.2016 - 00:31
fonte
-1

La terribilmente denominata lingua Vai su link offre più valori di ritorno, il secondo spesso è uno stato di errore. Invece di eccezioni. Guarda anche defer per eccezioni come catching. Probabilmente sarebbe adatto per sistemi embedded - il mio unico dubbio è di considerazioni strategiche.

    
risposta data 18.10.2016 - 09:25
fonte

Leggi altre domande sui tag