2011-08-09 20 views
6

Usar una macro definida para devolver un valor condicionalmente tiene una desventaja donde no es aparente solo mirando que el código del cliente puede salir en el punto de la macro.¿Es una mala práctica tener una definición de macro para devolver un valor para una función?

El caso de uso que estoy considerando es escribir una comprobación de valores y error, así:

#define WRITE_CHK(file, param)\ 
if (!write_that_returns_zero_on_fail(file, param)) {\ 
    handle_error();\ 
    return false;\ 
} 

código de cliente:

bool myfunc() 
{ 
    ... 
    WRITE_CHK(file, param) // function might return here 
    ... 
    return true; 
} 

Estoy ansioso por ver si los beneficios de la macro (que sería ser utilizado en muchos lugares en mi código) superaría la desventaja mencionada anteriormente. ¿Hay alternativas preferidas además de simplemente expandir (no usar la macro)?

+0

Bueno, el enfoque habitual de C++ es hacer que la función "escribir" arroje una excepción por error, en lugar de verificar constantemente los códigos de retorno.Tiene el mismo problema de "retorno temprano potencialmente sorprendente", pero también lo hace cualquier programa que use excepciones ... – Nemo

+0

Quiero una solución que sea c compatible –

+0

Háganme saber por qué mi pregunta es negativa para poder mejorar las futuras –

Respuesta

14

La respuesta estándar es "no usar macros"; pero eso a menudo es un poco simplista. A veces hay casos en los que pueden reducir en gran medida la verbosidad repetitiva que de lo contrario tendrías.

Entonces, ¿por qué no codificar el hecho en el nombre del macro? p.ej. WRITE_OR_RETURN_ON_FAILURE. Puede ser un poco detallado, pero es mucho menos probable que los lectores de su código tropiecen.

+1

Quiero darte +0.5 aquí. Su sugerencia es perfectamente válida, pero el enfoque de retorno en macro tiene un olor y una respuesta mejor implicaría un enfoque alternativo. +1 de todos modos. :) –

+4

@Jonathan: Sí, lo consideré. Pero a veces realmente solo quieres terminar ese patrón tedioso, y una función auxiliar simplemente no lo hará. –

+1

Bastante justo. Aún así, de esta manera mienten los dragones. –

1

Hacer una macro de 2 pasos:

#define WRITE_CHK_(file, param, HANDLER)\ 
if (!write_that_returns_zero_on_fail(file, param)) {\ 
    handle_error();\ 
    HANDLER\ 
} 

#define RFALSE return false; 

#define WRITE_CHK(file, param) WRITE_CHK_(file, param, RFALSE) 

entonces puede presentar otros envoltorios si necesita un control similar en otra parte (o utilizar directamente WRITE_CHK(file, param, return false))

+0

¿Cómo soluciona esto el problema? –

+1

@Oli Si desea que se manifieste que está devolviendo el mensaje falso, puede usar algo como 'WRITE_CHK (archivo, param, return false)'. –

+0

Sí, de hecho. ¡Creo que tu respuesta sería mejor si sugiriera ese enfoque! –

-4

Sí, es malo. Use un goto.

#define fail_unless(x) do {if(!(x)) goto fail;} while (0) 

int foo() 
{ 
    fail_unless(my_write(...)); 
    fail_unless(bar()); 
    return 0; 
fail: 
    cleanup(); 
    return -1; 
} 

Editar: si está downvoting porque usted no entiende lo que está haciendo bucle while ahí, sólo debería haber pedido. Es una técnica estándar para asegurarse de que una macro C actúa como una declaración única en cualquier contexto. Si la macro se expande a una sentencia if (como las macros en las otras respuestas, upvoted), entonces se obtiene efectos secundarios inesperados, por ejemplo:

#define DONT_DO_THIS(x) if (!x) { foo; } 

if (a) DONT_DO_THIS(x) 
else something_else(); 

Es de esperar something_else a ser ejecutado cuando !a, pero lo que el macro en realidad se expande a es:

if (a) 
    if (!x) { foo;} 
    else something_else(); 

Y something_else se ejecuta cuando a && x, no cuando el programador !a como se pretende.

+3

¿Hablas en serio? ¿Cómo es esto mejor para eliminar la sorpresa? –

+0

Por supuesto que hablo en serio. Ambos puntos de salida en la función son visibles, y la etiqueta 'fail' le dice lo que está sucediendo. Además, en lugar de utilizar macros como 'WRITE_CHK' de fsmc, que escribe y comprueba y potencialmente devuelve (y luego un' BAR_CHK' similar para la barra, etc.), hay una sola macro 'CHECK'. Lo llamé así para seguir su ejemplo, pero usaría algo como 'fail_check', fortaleciendo el vínculo entre la macro y la etiqueta. – LaC

+0

@LaC que implica aún más código y no generaliza bien –

2

Ocultar el flujo de control dentro de una macro no se utiliza con mucha frecuencia, por lo que puede ser confuso para los desarrolladores que tendrán que comprender o depurar su código.

Recomendaría no usar macros en su totalidad, pero si tiene que usarlas asegúrese de que la parte del flujo de control sea explícita.

// assume handle_error returns true 
#define WRITE_FAILED(file, param)\ 
(!write_that_returns_zero_on_fail(file, param) && handle_error()) 

// macro usage 
if(WRITE_FAILED(file, param)) 
{ 
    return; 
} 

Otra pregunta que debes hacerte es: ¿por qué quieres usar macros aquí de todos modos? Si fue para ocultar el flujo de control, esa no es una buena idea. ¿Hay alguna otra razón?

Cuestiones relacionadas