2010-05-20 8 views
7

Ci sono luoghi in cui controllo i puntatori validi prima di eseguire un'operazione con essi; questi controlli possono essere annidati abbastanza profondamente a volte.Nested If (x) checks - Modo migliore per scrivere questo?

Ad esempio, ho

if (a) 
{ 
    if (a->b()) 
    { 
    if (a->b()->c()) 
    { 
     a->b()->c()->DoSomething(); 
    } 
    } 
} 

Io davvero non mi piace l'aspetto di questo. C'è un modo per trasformare questo in qualcosa di più leggibile? Idealmente,

if (a && a->b() && a->b()->c()) 
{ 
... 
} 

sarebbe fantastico, ma ovviamente non funzionerebbe.

EDIT - nvm l'esempio che ho messo funziona come tutti hanno sottolineato. L'ho provato per vedere se funziona, ma c'era un bug nel mio codice nel mio test. duh!

+6

Perché non funziona? –

+3

Ovviamente * dovrebbe * funzionare nell'esempio che hai pubblicato. Sta succedendo qualcosa nei casi 'else' che hai omesso? –

+2

Cosa ti fa pensare che non funzionerebbe? La valutazione del cortocircuito praticamente garantisce che i due significhi la stessa cosa (a meno che tu non abbia sovraccaricato l'operatore &&). –

risposta

28

Perché quest'ultimo non funziona?

In C, & & & è un operatore di cortocircuito, quindi viene valutato da sinistra a destra e, se una valutazione è falsa, la valutazione si interrompe.

In realtà, si potrebbe scrivere:

a && a->b() && a->b()->c() && a->b()->c()->DoSomething(); 
+6

Se si può scrivere 'a && a-> b() && a-> b() -> c() && a-> b() -> c() -> DoSomething();' dipende dal tipo di ritorno di 'DoSomething'. Se 'DoSomething' restituisce void, non è legale. –

+0

@R Samuel Klatchko: Ciò si applica solo se si mette l'espressione in un'istruzione 'if', vero? ... Non è necessario nell'esempio di WhirlWind. –

+0

@Daniel - no, perché è l'RHS di un operatore &&, deve avere * qualche * valore. Una funzione void non è un RHS valido. – nsayer

5

tuo secondo esempio funziona in C/C++. Si mette in corto circuito quando viene colpito il primo valore FALSE.

+0

Funziona anche in Java. – nsayer

+0

@nsayer: Sì. Buon punto –

3

Perché 'ovviamente non funzionerebbe'? Poiché l'operatore && valuta solo il termine corretto se la sinistra è valida, la riscrittura è perfettamente sicura.

13

Citando K&R :

Espressioni collegati da && o || vengono valutati da sinistra a destra, ed è garantito che la valutazione si fermerà non appena la verità o falsità è noto.

Pertanto, l'ultimo esempio funzionerà perfettamente, come WhirlWind has noted.


The C Programming Language, seconda edizione, pagina 21.

+0

Grazie per la citazione. Ho provato google per esattamente questo tipo di cose ma non avevo idea di cosa parole per google. Inutile dire che non l'ho trovato, e quando l'ho testato personalmente per vedere se valuto da sinistra a destra, ho riscontrato un errore che ha causato l'arresto. – Will

2

if (a && a->b() && a->b()->c()) { a->b()->c()->DoSomething(); }

C++ esegue la valutazione pigra modo questo funzionerà. Innanzitutto, verrà valutato e se è 0 l'intera condizione è falsa, quindi non ci sarà alcuna valutazione delle altre parti.

Questo funziona anche per l'operatore ||. Se scrivi if (a || b), b non verrà valutato se a è vero.

4

Hai visto dalle altre risposte che l'utilizzo di & & funzionerà e cortocircuiterà la valutazione quando viene rilevato un puntatore nullo.

Il programmatore inquieto in me ama evitare di ripetere le chiamate di metodo per test come questo poiché evita di preoccuparsi se sono idempotenti o meno.Una possibilità è quella di riscrivere come questo

A* a; 
B* b; 
C* c; 

if ((a=a()) && (b=a->b()) && (c=b->c())) { 
    c->doSomething(); 
} 

Certo prolisso e un po 'goffo, ma almeno si sa ogni metodo viene chiamato solo una volta.

+0

Se 'a' è un puntatore, l'invocazione che usa non deve essere 'a-> b()'? –

+0

d'oh - si. Questo è quello che intendevo, ma non ho scritto. Ho fatto java per troppo tempo ... – mdma

0

Secondo funzionerà se si desidera chiamare solo DoSomething().

3

Dato che hai già ricevuto la risposta diretta alla tua domanda, ti accennerò semplicemente che le lunghe catene di chiamate come quelle che hai ricevuto sono un odore di codice e potresti considerare un design migliore. un tale progetto potrebbe, in questo caso, includere l'uso del modello oggetto null in modo che la chiamata potrebbe anche ridursi a:

a->CDoSomething(); 
+1

[Law of Demeter] (http://en.wikipedia.org/wiki/Law_of_Demeter) –

3

opere concatenamento, ma non è necessariamente la migliore risposta generale e minuscole, soprattutto perché oscura il punto di errore. Vorrei invece suggerire di appiattire i test invertendo la logica in modo che esca in caso di fallimento.

if (!pa) 
    return Fail("No pa"); 

B* pb = pa->b(); 
if (!pb) 
    return Fail("No pb"); 

C* pc = b->c(); 
if (!pc) 
    return Fail("No pc"); 

pc->DoSomething(); 

Stessa cosa, ma piatta e facile da leggere. Inoltre, poiché gestisce immediatamente il caso di errore, questo non viene relegato a un else che potresti non riuscire mai a scrivere.

In questo esempio, ho pensato che non volessi semplicemente fallire, quindi ho aggiunto Fail come aiuto che registra il testo e restituisce false. Potresti anche solo lanciare un'eccezione. Infatti, se i vari metodi segnalavano il loro fallimento lanciando un'eccezione appropriata invece di restituire nulla, allora tutto ciò non sarebbe necessario. Se fosse auspicabile un errore silenzioso, sarebbe opportuno un modello di oggetto nullo.

Problemi correlati