Tutti i processi di progettazione comportano compromessi tra obiettivi reciprocamente incompatibili. Sfortunatamente, il processo di progettazione per l' &&operatore sovraccarico in C ++ ha prodotto un risultato finale confuso: che la stessa caratteristica che si desidera &&- il suo comportamento di corto circuito - è omessa.
I dettagli di come è finito quel processo di progettazione in questo luogo sfortunato, quelli che non conosco. È tuttavia rilevante vedere come un successivo processo di progettazione abbia tenuto conto di questo risultato spiacevole. In C #, l' &&operatore sovraccarico è in corto circuito. Come sono riusciti i progettisti di C #?
Una delle altre risposte suggerisce "sollevamento lambda". Questo è:
A && B
potrebbe essere realizzato come qualcosa moralmente equivalente a:
operator_&& ( A, ()=> B )
dove il secondo argomento utilizza alcuni meccanismi per la valutazione pigra in modo che, quando valutati, vengano prodotti gli effetti collaterali e il valore dell'espressione. L'implementazione dell'operatore sovraccarico farebbe la valutazione pigra solo quando necessario.
Questo non è ciò che ha fatto il team di progettazione di C #. (A parte: se sollevamento lambda è quello che ho fatto quando è arrivato il momento di fare rappresentazione albero espressione del ??operatore, che richiede alcune operazioni di conversione essere effettuato pigramente Descrivendo che in dettaglio sarebbe tuttavia un importante digressione Basti dire:.. Sollevamento lambda funziona ma è sufficientemente pesante da volerlo evitare.)
Piuttosto, la soluzione C # suddivide il problema in due problemi separati:
- dovremmo valutare l'operando di destra?
- se la risposta a quanto sopra è stata "sì", come combiniamo i due operandi?
Pertanto, il problema viene risolto rendendo illegale il sovraccarico &&diretto. Piuttosto, in C # devi sovraccaricare due operatori, ognuno dei quali risponde a una di quelle due domande.
class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(A parte: in realtà, tre. C # richiede che se falseviene fornito l'operatore, trueanche l' operatore deve essere fornito, il che risponde alla domanda: questa cosa è "vero-vero?". In genere non ci sarebbe motivo di fornire solo uno di questi operatori, quindi C # richiede entrambi.)
Considera una dichiarazione del modulo:
C cresult = cleft && cright;
Il compilatore genera codice per questo come si pensava che tu avessi scritto questo pseudo-C #:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Come puoi vedere, il lato sinistro viene sempre valutato. Se si determina che è "falso-ish", allora è il risultato. Altrimenti, viene valutato il lato destro e viene richiamato l' operatore desideroso definito dall'utente &.
L' ||operatore è definito in modo analogo, come un'invocazione dell'operatore vero e |dell'operatore desideroso :
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Con la definizione di tutti e quattro gli operatori - true, false, &e |- C # consente non solo di dire cleft && cright, ma anche non-corto circuito cleft & cright, e anche if (cleft) if (cright) ..., e c ? consequence : alternativee while(c), e così via.
Ora, ho detto che tutti i processi di progettazione sono il risultato di un compromesso. Qui i progettisti del linguaggio C # sono riusciti a ottenere cortocircuiti &&e ||giusti, ma per farlo è necessario sovraccaricare quattro operatori anziché due , cosa che alcune persone trovano confusa. La funzione true / false dell'operatore è una delle funzionalità meno conosciute in C #. L'obiettivo di avere un linguaggio sensibile e diretto, familiare agli utenti di C ++, era contrastato dai desideri di corto circuito e dal desiderio di non implementare il lambda lifting o altre forme di valutazione pigra. Penso che fosse una posizione di compromesso ragionevole, ma è importante rendersi conto che si tratta di una posizione di compromesso. Solo un diverso posizione di compromesso rispetto a quella dei progettisti di C ++.
Se l'argomento del design del linguaggio per tali operatori ti interessa, considera di leggere le mie serie sul perché C # non definisce questi operatori su valori booleani nulla:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)