2010-05-20 15 views
7

Hay lugares donde verifico si hay punteros válidos antes de realizar una operación con ellos; estos controles se pueden anidar bastante profundamente a veces.Anidado Si (x) comprueba - ¿Mejor forma de escribir esto?

Por ejemplo, he

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

Realmente no me gusta el aspecto de este. ¿Hay alguna manera de convertir esto en algo más legible? Idealmente,

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

sería genial, pero obviamente no funcionaría.

EDITAR - nvm el ejemplo que puse SÍ funciona como todo el mundo ha señalado. Lo probé para ver si funciona, pero hubo un error en mi código en mi prueba. duh!

+6

¿Por qué no funciona? –

+3

Obviamente * funcionaría * en el ejemplo que ha publicado. ¿Está sucediendo algo en los casos de 'else' que dejaste fuera? –

+2

¿Qué te hace pensar que eso no funcionaría? La evaluación de cortocircuito prácticamente garantiza que los dos significan lo mismo (a menos que haya sobrecargado 'operator &&'). –

Respuesta

28

¿Por qué este último no funcionaría?

En C, & & es un operador de cortocircuito, por lo que se evalúa de izquierda a derecha, y si la evaluación es falsa, la evaluación se detiene.

De hecho, se podría escribir:

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

Si puede escribir 'a && a-> b() && a-> b() -> c() && a-> b() -> c() -> DoSomething();' depende del tipo de retorno de 'DoSomething'. Si 'DoSomething' devuelve nulo, eso no es legal. –

+0

@R Samuel Klatchko: Eso solo se aplicaría si pones la expresión en una declaración 'if', ¿no es así? ... No hay necesidad de hacerlo en el ejemplo de WhirlWind. –

+0

@Daniel - no, porque es el RHS de un operador &&, debe tener * algún * valor. Una función nula no es una RHS válida. – nsayer

5

Su segundo ejemplo hace el trabajo en C/C++. Se cortocircuita cuando se acelera el primer valor FALSO.

+0

Funciona en Java también. – nsayer

+0

@nsayer: Sí. Buen punto. –

3

¿Por qué 'obviamente no funcionaría'? Como el operador && solo evalúa el término correcto si el izquierdo es válido, la reescritura es perfectamente segura.

13

Citando K&R :

expresiones conectadas por && o || se evalúan de izquierda a derecha, y está garantizado que la evaluación se detendrá tan pronto como la verdad o la falsedad es conocido.

Por lo tanto, el último ejemplo funcionará perfectamente, como WhirlWind has noted.


The C Programming Language, segunda edición, página 21.

+0

Gracias por la cotización. Intenté buscar exactamente este tipo de cosas en Google, pero no tenía idea de qué palabras usar en Google. Huelga decir que no encontré esto, y cuando lo probé para ver si se evalúa de izquierda a derecha, tuve un error que causó su bloqueo. – Will

2

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

C++ realiza la evaluación perezosa de modo que esto funcionará. Primero, se evaluaría a y si es 0 la condición completa es falsa, por lo que no habrá evaluación de las otras partes.

Esto también funciona para el operador ||. Si escribe if (a || b), b no se evaluará si a es verdadero.

4

Usted ha visto por las otras respuestas que usar & & funcionará, y pondrá en cortocircuito la evaluación cuando se encuentre un puntero nulo.

El incómodo programador en mi me gusta evitar repetir las llamadas a métodos para pruebas como esta ya que evita preocuparse si son idempotentes o no.Una opción es volver a escribir como esto

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

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

cierto detallado y un poco torpe, pero al menos se sabe cada método se llama sólo una vez.

+0

Si 'a' es un puntero, ¿la invocación no debe ser 'a-> b()'? –

+0

d'oh - sí. Eso es lo que quise decir, pero no escribí. He estado haciendo Java por mucho tiempo ... – mdma

0

El segundo funcionará siempre que desee llamar solo al DoSomething().

3

Dado que ya has recibido la respuesta directa a tu pregunta, mencionaré que las largas cadenas de llamadas que recibes son un olor a código y puedes considerar un mejor diseño. Dicho diseño podría, en este caso, incluir el uso del modelo de objeto nulo de manera que su llamada puede simplemente se reducen a:

a->CDoSomething(); 
+1

[Ley de Demeter] (http://en.wikipedia.org/wiki/Law_of_Demeter) –

3

obras encadenamiento, pero no es necesariamente la mejor respuesta a caso general, sobre todo porque oscurece el punto de falla. En cambio, sugeriría aplanar las pruebas invirtiendo la lógica para que salga de la falla.

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(); 

Lo mismo, pero plano y fácil de leer. Además, debido a que maneja inmediatamente el caso de falla, eso no se relega a un else que quizás nunca escriba.

En este ejemplo, asumí que no quería simplemente fallar silenciosamente, así que agregué Fail como un ayudante que registra el texto y devuelve falso. También podrías lanzar una excepción. De hecho, si los diversos métodos señalaran su falla lanzando una excepción apropiada en lugar de devolver un valor nulo, entonces todo esto sería innecesario. Si la falla silenciosa era deseable, entonces un patrón de objeto nulo sería apropiado.

Cuestiones relacionadas