2010-07-27 15 views
9

He cambiado un poco el título porque pensé que esta era la pregunta más adecuada.C++, ¿está justificada esta declaración goto?

¿Lo refactorizaría (parece uso legítimo de goto)? Si, ¿cómo podría refactorizar el siguiente código para eliminar ir a la declaración?

if (data.device) { 
    try { 
     ... 
    } 
    catch(const std::exception&) { goto done; } 
    ... // more things which should not be caught 
done: ; 
} 

declaración completa

#ifdef HAVE_GPU 
      // attempt to use GPU device 
      if (data.device) { 
       try { 
        Integral::Gpu eri(S, R, Q, block.shell()); 
        eri(basis.centers(), quartets, data.device); 
       } 
       // if GPU fails, propagate to cpu 
       catch(std::exception) { goto done; } 
       data.device += size; 
       host_index.extend(block_index); 
       block_index.data.clear(); 
      done: ; 
      } 
#endif 

agradecimiento

después de haber visto la preferencia de la mayoría, me decidí a ir con la bandera, pero con la observación del Sr. York.

Gracias a todos

+2

Es difícil de decir sin ver lo que hay en los bloques, pero otra posibilidad es mover el código dentro de la función if a. (Puede mejorar la legibilidad también, tal vez no). Luego puede regresar temprano si algo malo sucedió. – GManNickG

+4

Catch by const reference, please. –

+0

@GMan agregó una declaración completa. La función puede ser exagerada, ya que no hay cierre. – Anycorn

Respuesta

15
if (data.device) 
{ 
    bool status = true; 

    try 
    { 
     ... 
    } 
    catch(std::exception) 
    { 
     status = false; 
    } 

    if (status) 
    { 
    ... // more things which should not be caught 
    } 
} 
+0

puede ser una respuesta algo obvia, pero ¿cuál prefieres? ¿Estado o goto? – Anycorn

+1

@aaa: Puede que no sea la mejor persona para preguntar, ya que soy un antigotoite no arrepentido. :-) Hoy he sido rechazado por responder una pregunta con "Así es cómo lo harías sin ir a goto: ..." Pero mis razones tienen más que ver con la escalabilidad y la ortogonalidad que con la antipatía por la palabra clave. –

+5

¿Por qué la gente insiste en introducir nuevas variables y ramas solo para evitar un goto? Deje de beber la ayuda de kool de los documentos "Considerados nocivos" (mejor aún, lea el documento y verá que Dijkstra no los guarda nunca) y de hecho piense un poco acerca de lo que está haciendo. Agregaste más estado y más ramas a tu código solo para satisfacer "No usé' goto' ". –

2

¿No puede ver la excepción fuera de if?

+0

otra lógica lo haría muy feo, por lo que preferiría no – Anycorn

3
if (data.device) { 
    bool ok = true; 
    try { 
     ... 
    } 
    catch(std::exception) { ok = false; } 

    if(ok) { 
     ... // more things which should not be caught 
    } 
} 
+0

La misma respuesta que se muestra arriba en una de las respuestas anteriores. –

7

se puede coger la excepción y volver a lanzar una excepción específica que puede ser manejado fuera del bloque condicional.

// obviously you would want to name this appropriately... 
struct specific_exception : std::exception { }; 

try { 
    if (data.device) { 
     try { 
      // ... 
     } 
     catch(const std::exception&) { 
      throw specific_exception(); 
     } 

     // ... more things which should not be caught ... 
    } 
} 
catch (const specific_exception&) { 
    // handle exception 
} 
+1

gracias. Lo he pensado pero agrega más complejidad en mi opinión que goto/flag – Anycorn

+0

@aaa: Esto es, en mi opinión, mucho más limpio que cualquiera de esas opciones. Un 'goto' es completamente injustificado en este caso, y si usa un indicador de estado, termina mezclando dos mecanismos diferentes de notificación de fallas (indicadores de estado y excepciones) en un solo bloque de código, lo que aumenta enormemente la complejidad y hace que sea más difícil razón sobre el código. –

+0

¿Cuál es el significado de * goto está garantizado/injustificado *? El comportamiento de 'goto' está bien definido y (creo) hará lo que cabría esperar, ¿o no? Estoy de acuerdo en que agregar un bool check * * no es el camino a seguir de todos modos. –

1

¿Qué tal si usamos algunos indicadores y agregamos un enunciado condicional?

int caught = 0; 
if (data.device) { 
    try { 
     /* ... */ 
    } catch (std::exception e) { caught = 1; } 
    if (!caught) { 
     // more stuff here 
    } 
    // done: ... 
} 
+5

¿Tiene algo en contra del tipo 'bool'? –

8

Primero: goto is not evil per se. Refactorizar por el simple hecho de no tener las letras "goto" en tu código es una tontería. Refactándolo a algo que hace lo mismo más limpio que un goto está bien. Cambiar un diseño malo en uno que es mejor y no necesita un goto o un reemplazo para él también está bien.

Dicho esto, diría que su código se ve exactamente como para lo que finalmente se inventó. Demasiado triste C++ no tiene algo como esto ... así que tal vez la solución más fácil es dejarlo así.

+0

¡Infierno, sí! Vete! ¡Ve! Ve! Ve! ¡a! – Gianni

+3

Desafortunadamente,() no es la solución correcta aquí, ya que el código solo se usa cuando no hay excepción, finalmente, por otro lado, significa que siempre debe ejecutarse (incluso cuando hay excepciones). Aunque finalmente se requiere para lenguajes como C# y Java, no se requiere para C++ ya que existen mejores técnicas para implementar lo mismo. Aunque estoy de acuerdo con todos tus otros puntos. No arregles lo que no está roto. –

+1

Vota por el uso correcto y la ortografía de 'per se'. –

2

Cuando hay un montón de código que necesita salir en función de alguna condición, mi construcción preferida es usar un bucle "do {} while (0)" y "romper" cuando sea apropiado. No sé qué descanso; lo hará en una captura, sin embargo. Un "goto" podría ser su mejor opción si "break" no funciona.

+0

Eso es peor que un 'goto' en todos los sentidos. – Thomas

+0

+1 Eso es tan malo como el 'goto', pero es mejor que agregar una variable adicional y tener que agregar condiciones en el código. Y me inclino a pensar que cualquiera de estos es mejor que lanzar un nuevo tipo de excepción ... Todavía prefiero el 'goto', pero diablos esta respuesta merece nada menos que los demás :) –

1
catch(std::exception) { return; } 

Que debe hacer el truco. Estoy, por supuesto, suponiendo que done está de hecho al final de una función.

Si necesita ejecutar código adicional cuando se produce la excepción, regrese un estado o ejecute una excepción que se encuentre en un nivel de abstracción más adecuado (consulte la respuesta de James).

estoy imaginando algo como:

doStuff(...) { 
    bool gpuSuccess = doGPUStuff(...); 
    if (!gpuSuccess) { 
     doCPUStuff(...); 
    } 
} 
+0

Y omite todo lo demás en la función. – GManNickG

+0

Que bien puede ser lo correcto, o al menos refactorizarse. –

+2

No creo que 'done:' sea actualmente el final de una función; es seguido por el código para usar la CPU en lugar de la GPU (que debe haber fallado o haberse compilado). –

4

Por qué se mueve el código adicional dentro del bloque try ?:

#ifdef HAVE_GPU 
      // attempt to use GPU device 
      if (data.device) { 
       try { 
        Integral::Gpu eri(S, R, Q, block.shell()); 
        eri(basis.centers(), quartets, data.device); 
        data.device += size; 
        host_index.extend(block_index); 
        block_index.data.clear(); 
       } 
       // if GPU fails, propagate to cpu 
       catch(std::exception) { /* handle your exception */; } 
      } 
#endif 
+1

El código de OP deja en claro que las dos primeras líneas contienen un punto de ruptura nominal que puede arrojarse. Tu ejemplo se tragaría todas las excepciones. 'host_index.extend() 'ahora puede fallar silenciosamente. –

+0

Básicamente su refrán, ponga la cantidad mínima de código dentro de un bloque try como sea posible? –

+0

Si dejó eso en claro, no estaba en la pregunta. Tenga en cuenta el comentario "manejar su excepción" dentro de la captura. Depende de la OP escribir ese código. –

4

Creo que una variante de esto podría funcionar para usted.

// attempt to use GPU device 
if (data.device) 
{ 
    try 
    { 
     Integral::Gpu eri(S, R, Q, block.shell()); 
     eri(basis.centers(), quartets, data.device); 

     data.device += size; 
     host_index.extend(block_index); 
     block_index.data.clear(); 
    } 
    catch (const std::bad_alloc&) 
    { 
     // this failure was not because 
     // of the GPU, let it propagate 
     throw; 
    } 
    catch(...) 
    { 
     // handle any other exceptions by 
     // knowing it was the GPU and we 
     // can fall back onto the CPU. 
    } 
} 

// do CPU 

Si usted podría editar la biblioteca GPU y dar todas las excepciones GPU alguna base como gpu_exception, el código se vuelve mucho más sencillo:

// attempt to use GPU device 
if (data.device) 
{ 
    try 
    { 
     Integral::Gpu eri(S, R, Q, block.shell()); 
     eri(basis.centers(), quartets, data.device); 

     data.device += size; 
     host_index.extend(block_index); 
     block_index.data.clear(); 
    } 
    catch (const gpu_exception&) 
    { 
     // handle GPU exceptions by 
     // doing nothing and falling 
     // back onto the CPU. 
    } 

    // all other exceptions, not being 
    // GPU caused, may propagate normally 
} 

// do CPU 

Si abisal de estos trabajos, creo que la mejor alternativa es Steve's answer.

4

Uso un poco diferente de una bandera. Creo que es más limpio que el de Amardeep.

Prefiero usar una bandera para indicar si se debe propagar la excepción, que una bandera para indicar si la última cosa funcionó, porque el punto entero de excepciones es para evitar verificaciones de si lo último funcionó - la idea es escribir un código tal que, si llegamos tan lejos, todo funcionó y estamos en condiciones de continuar.

#ifdef HAVE_GPU 
    // attempt to use GPU device 
    if (data.device) { 
     bool dont_catch = false; 
     try { 
      ... 
      dont_catch = true; 
      ... // more things which should not be caught 
     } catch (...) { 
      if (dont_catch) throw; 
     } 
    } 
#endif 
+0

Oh, buen pensamiento. :) – GManNickG

+1

@GMan: cambio realizado: es un comportamiento diferente al código del interrogador, por supuesto, pero guarda una discusión sobre el valor frente a la referencia ;-) –

2

Me estoy perdiendo algo, o ¿no sería equivalente a mover la parte entre el catch y done: etiqueta dentro del bloque de try?

#ifdef HAVE_GPU 
      // attempt to use GPU device 
      if (data.device) { 
       try { 
        Integral::Gpu eri(S, R, Q, block.shell()); 
        eri(basis.centers(), quartets, data.device); 
        data.device += size; 
        host_index.extend(block_index); 
        block_index.data.clear(); 
       } 
       // if GPU fails, propagate to cpu 
       catch(std::exception) {} 
      } 
#endif 
+1

+1: * Si * esto es lo que el OP quiere hacer, es el la solución más simple. –

+0

Sí, te estás perdiendo algo. Ahora, si algo que estuvo fuera del try antes de los throws, su excepción solo se come. – GManNickG

1

Sólo romper hacia fuera en su propia función/método (incluyendo todo, después de él) y el uso de la palabra clave return. Siempre que todas sus variables tengan destructores, sean asignadas en pila (o puntadas inteligentes si es inevitable), entonces está bien. Soy un gran admirador de las funciones/métodos de salida temprana en lugar de la verificación continua de banderas.

por ejemplo:

void myFunc() 
{ 
    String mystr("heya"); 
    try 
    { 
     couldThrow(mystr); 
    } 
    catch(MyException& ex) 
    { 
     return; // String mystr is freed upon returning 
    } 

    // Only execute here if no exceptions above 
    doStuff(); 
} 

De esta manera, es difícil equivocarse

+0

De acuerdo. Solo póngalo en una función, y regrese cuando quiera, bueno, regrese. – jalf

1

La razón Goto está mal visto en la actualidad es que tenemos estas cosas de lujo denominan "funciones" en su lugar. Envuelva el código GPU en su propia función, que puede regresar temprano si falla.

Luego llame a eso desde su función original.

ya que probablemente tendrán que compartir algunas variables (data, host_index y block_index, parece), ponerlos en una clase, y crea los dos miembros de las funciones de la misma.

void RunOnGpu(){ 
     // attempt to use GPU device 
     if (data.device) { 
      try { 
       Integral::Gpu eri(S, R, Q, block.shell()); 
       eri(basis.centers(), quartets, data.device); 
      } 
      // if GPU fails, propagate to cpu 
      catch(std::exception) { return; } 
      data.device += size; 
      host_index.extend(block_index); 
      block_index.data.clear();  
} 
void DoStuff() { 
#ifdef HAVE_GPU 
    RunOnGpu(); 
#endif 
} 
Cuestiones relacionadas