24 votos

Forma elegante de salir de una función perfectamente sin el uso de goto en C

Nos escriben a menudo algunas de las funciones que tiene más de un punto de salida (es decir, return en C). Al mismo tiempo, al salir de la función, para algunas obras generales como de los recursos de la limpieza, queremos implementar ellos sólo una vez, en lugar de la aplicación de ellos en cada punto de salida. Normalmente, podemos lograr nuestro deseo por el uso de goto como la siguiente:

void f()
{
    ...
    ...{..{... if(exit_cond) goto f_exit; }..}..
    ...
    f_exit:
    some general work such as cleanup
}

Creo que el uso de goto aquí es aceptable, y sé que muchas personas están de acuerdo en el uso de goto aquí. Sólo por curiosidad, ¿existe alguna manera elegante perfectamente para salir de una función sin el uso de goto en C?

31voto

jxh Puntos 32720

¿Por qué evitar el goto?

El problema que se quiere resolver es: ¿Cómo hacer seguro de que algunos de código común, siempre se ejecuta antes de que la función devuelve a la persona que llama? Esto es un problema para los programadores de C, ya que C no proporciona ningún apoyo para RAII.

Como ya ceder en la cuestión del cuerpo, goto es una perfecta solución aceptable. Nunca-el-menos, no puede ser no-razones técnicas para evitar el uso de:

  • ejercicio académico
  • codificación de cumplimiento de la norma
  • personal capricho (que creo que es lo que está motivando a esta pregunta)

Siempre hay más de una manera para la piel de un gato, pero la elegancia como un criterio es demasiado subjetiva para proporcionar una manera para limitar a un único mejor alternativa. Usted tiene que decidir cuál es la mejor opción para usted.

Una llamada explícita a una función de limpieza de

Si se evita un explícito saltar (por ejemplo, goto o break) común código de limpieza puede ser encapsulado dentro de una función, y se llama explícitamente en el punto de comienzos return.

int foo () {
    ...
    if (SOME_ERROR) {
        return foo_cleanup(SOME_ERROR_CODE, ...);
    }
    ...
}

(Esto es similar a otro publicado respuesta, que sólo la vi después de que inicialmente publicado, pero la forma que se muestra aquí puede tomar ventaja de los hermanos de la llamada optimizaciones.)

Algunas personas sienten lo explícito es más clara, y por lo tanto más elegante. Otros sienten la necesidad de pasar de limpieza de los argumentos de la función a ser un gran detractor.

Añadir otra capa de indirección.

Sin cambiar la semántica del usuario de la API, el cambio de su aplicación en un contenedor compuesto de dos partes. Parte uno realiza el trabajo real de la función. La segunda parte realiza la limpieza necesaria después de la primera parte se hace. Si cada parte está encapsulado dentro de su propia función, la función de contenedor tiene un muy limpio implementación.

struct bar_stuff {...};

static int bar_work (struct bar_stuff *stuff) {
    ...
    if (SOME_ERROR) return SOME_ERROR_CODE;
    ...
}

int bar () {
    struct bar_stuff stuff = {};
    int r = bar_work(&stuff);
    return bar_cleanup(r, &stuff);
}

El "implícita" de la naturaleza de la limpieza desde el punto de vista de la función que realiza el trabajo puede ser visto favorablemente por algunos. Algunas de las potenciales código de la hinchazón también puede evitarse por sólo llamando a la función de limpieza de un solo lugar. Algunos argumentan que "implícita" conductas son "difíciles", y por lo tanto más difícil de entender y de mantener.

Miscelánea...

Más esotéricas de soluciones mediante el uso de setjmp()/longjmp() puede ser considerada, pero utilizando de manera correcta puede ser difícil. No son de código abierto contenedores que implementar try/catch estilo de macros a través de ellos (por ejemplo, cexcept), pero usted tiene que cambiar su estilo de codificación a utilizar ese estilo para el manejo de errores.

También se podría considerar la aplicación de la función como una máquina de estado. La función de progreso a través de cada estado, un error hace que la función de corto circuito para la limpieza de estado. Este estilo es generalmente reservado particularmente complejos, funciones o funciones que deben ser intentar más tarde y ser capaz de recoger, desde donde la dejaron.

Hacer como los Romanos.

Si usted necesita para cumplir con los estándares de codificación, a continuación, el mejor enfoque es seguir lo que técnica es la más frecuente en la base del código existente. Esto se aplica a casi todos los aspectos de hacer cambios en los existentes estable base de código fuente. Se considera perjudicial para introducir un nuevo estilo de codificación. Usted debe buscar la aprobación de los poderes que si usted se siente un cambio dramáticamente mejorar algún aspecto del software. De lo contrario, como la "elegancia" es subjetiva, argumentando que por el bien de la "elegancia" no va a llegar a ninguna parte.

6voto

Vlad from Moscow Puntos 36219

Por ejemplo

void f()
{
    do
    {
         ...
         ...{..{... if(exit_cond) break; }..}..
         ...
    }  while ( 0 );

    some general work such as cleanup
}

O usted podría utilizar la siguiente estructura

while ( 1 )
{
   //...
}

La principal ventaja del enfoque estructural contrario al uso de goto es la que introduce una disciplina en la escritura de código.

Estoy seguro de que tiene suficiente experiencia que si una función tiene una instrucción goto, a continuación, a través de cierto tiempo se tienen varios goto.:)

5voto

Lundin Puntos 21616

He visto un montón de soluciones de cómo hacerlo y que tienden a ser oscuras, ilegible y feo en algún grado.

Yo personalmente creo que el menos feo es este:

int func (void)
{
  if(some_error)
  {
    cleanup();
    return result;
  }

  ...

  if(some_other_error)
  {
    cleanup();
    return result;
  }

  ...

  cleanup();
  return result;
}

Sí, utiliza dos líneas de código en lugar de uno. Así? Es claro, legible, mantenible. Este es un ejemplo perfecto de donde tienes que luchar contra su knee-jerk reflejos contra la repetición de códigos y usar el sentido común. La función de limpieza está escrito sólo una vez, todo el código de limpieza es centralizado allí.

5voto

midor Puntos 486

Creo que la pregunta es muy interesante, pero no puede ser contestada sin ser influenciado por la subjetividad, porque la elegancia es subjetiva. Mis ideas son como sigue: En general, ¿qué quieres hacer en la situación que usted describe, es para evitar el control de pasar a través de una serie de declaraciones a lo largo de la ruta de ejecución. Otros idiomas puede hacer esto mediante la excepción, la que tendría que coger.

Yo ya había escrito limpio hacks para hacer lo que quieres hacer con casi todos sentencia de control que hay en C, a veces en combinación, pero creo que todos son muy oscuras formas de expresar la idea de saltar a un punto en especial. En lugar de eso me voy a hacer mi punto de vista en cómo llegamos a un punto donde goto puede ser preferible : una Vez más, lo que se quiere expresar por medio es que ha ocurrido algo que impide que tras la ejecución regular de ruta. Algo que no es sólo una condición regular que puede ser manejado por tomar una rama diferente por el camino, pero algo hace que sea imposible utilizar la ruta de acceso regular a punto de retorno en condiciones de seguridad en el estado actual. Creo que hay tres opciones para continuar en ese punto:

  1. de retorno a través de una cláusula condicional
  2. ir a un error de etiqueta
  3. cada declaración que podría fallar es en el interior de un enunciado condicional, y regular la ejecución se considera una serie de condicionales operaciones.

Si la limpieza es bastante similar en todo lo posible la salida de emergencia prefiero el de goto, ya que al escribir el código de forma redundante sólo satura la función. Yo creo que debe de comercio el número de puntos de reciclaje y replicado código de limpieza, que cree en contra de la incomodidad de usar un goto. Ambas soluciones deben ser aceptadas como una elección personal del programador, a menos que existan graves motivos para no hacerlo, por ejemplo, se acordó que todas las funciones deben tener una única salida. Sin embargo, el uso de cualquiera de las debe ser consecuente y coherente en todo el código. La tercera alternativa es - imo - menos legible primo de goto, porque, al final va a saltar a un conjunto de rutinas de limpieza - posiblemente cerrado por lo demás, las declaraciones demasiado, pero hace que sea mucho más difícil para los seres humanos para seguir el flujo regular del programa, debido a la profundidad de anidamiento de sentencias condicionales.

tl;dr: creo que la elección entre condicional de retorno y goto basado en la consecuente estilo de decisiones es la forma más elegante, porque es el más expresivo para representar sus ideas detrás de el código y la claridad es la elegancia.

4voto

Basile Starynkevitch Puntos 67055

Supongo que elegante puede significar que te extraño y que simplemente quiere evitar el goto de palabras clave, de modo que....

Usted podría considerar el uso de setjmp(3) y longjmp :

void foo() {
jmp_buf jb;
if (setjmp(jb) == 0) {
   some_stuff();
   //// etc...
   if (bad_thing() {
       longjmp(jb, 1);
   }
 };
};

No tengo idea de si se ajusta a su elegancia criterios. (Creo que no es muy elegante, pero esto es sólo una opinión; sin embargo, no hay ninguna explícita goto).

Sin embargo, lo interesante es que longjmp es un no-local de salto : podría haber pasado (indirectamente) jb a some_stuff y algunos otros de rutina (por ejemplo, llamados por some_stuff) longjmp. Esto puede llegar a ser ilegible el código (modo de comentario sabiamente).

Incluso más feo que longjmp : uso (en Linux) setcontext(3)

Leer acerca de las continuaciones y las excepciones (y la llamada/cc operación en el Esquema).

Y, por supuesto, el estándar de salida(3) es una elegante (y útil) manera de salir de alguna función. A veces se podía jugar al truco, utilizando también el atexit(3)

Por CIERTO, el kernel de Linux de código se utiliza con bastante frecuencia goto incluidos en un código que es considerado como elegante.

Mi punto es : en mi humilde opinión no ser fanático contra goto-s , ya que hay casos donde el uso (con cuidado) es en efecto elegante.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X