while usando resueltos for ejemplos dev bucle c++ do-while

usando - factorial c++ do while



¿Cuáles son algunas mejores maneras de evitar el hacer mientras que(0); hackear en C++? (27)

Cuando el flujo de código es así:

if(check()) { ... ... if(check()) { ... ... if(check()) { ... ... } } }

Generalmente he visto este trabajo para evitar el flujo de código desordenado anterior:

do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0);

¿Cuáles son algunas de las mejores maneras de evitar esta solución / piratería para que se convierta en un código de nivel superior (nivel industrial)?

Cualquier sugerencia que esté fuera de la caja son bienvenidas!


  1. Intente extraer el código en una función separada (o quizás más de una). Luego volverá de la función si la comprobación falla.

  2. Si está demasiado unido al código circundante para hacer eso, y no puede encontrar una manera de reducir el acoplamiento, mire el código después de este bloque. Presumiblemente, limpia algunos recursos utilizados por la función. Intente administrar estos recursos utilizando un objeto RAII ; luego reemplaza cada break con return (o throw , si es más apropiado) y deja que el destructor del objeto limpie por ti.

  3. Si el flujo del programa es (necesariamente) tan ondulado que realmente necesitas un goto , entonces goto lugar de darle un extraño disfraz.

  4. Si tiene reglas de codificación que prohíben ciegamente el goto , y realmente no puede simplificar el flujo del programa, entonces probablemente tendrá que disfrazarlo con su pirata informático.


¿Qué tal si movemos las declaraciones if a una función adicional que produzca un resultado numérico o enumeración?

int ConditionCode (void) { if (condition1) return 1; if (condition2) return 2; ... return 0; } void MyFunc (void) { switch (ConditionCode ()) { case 1: ... break; case 2: ... break; ... default: ... break; } }


Algo como esto quizás

#define EVER ;; for(EVER) { if(!check()) break; }

o usar excepciones

try { for(;;) if(!check()) throw 1; } catch() { }

Usando excepciones también puede pasar datos.


Así es como lo hago.

void func() { if (!check()) return; ... ... if (!check()) return; ... ... if (!check()) return; ... ... }


Ciertamente no es la respuesta, sino una respuesta (en aras de la integridad)

En lugar de :

do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0);

Podrías escribir:

switch (0) { case 0: if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... }

Esto sigue siendo un goto disfrazado, pero al menos ya no es un bucle. Lo que significa que no tendrá que comprobar con mucho cuidado que no hay algo que continúe escondido en algún lugar del bloque.

La construcción también es lo suficientemente simple como para que puedas esperar que el compilador la optimice.

Como lo sugiere @jamesdlin, incluso puede ocultar eso detrás de una macro como

#define BLOC switch(0) case 0:

Y usalo como

BLOC { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... }

Esto es posible porque la sintaxis del lenguaje C espera una declaración después de un cambio, no un bloque entre corchetes y puede poner una etiqueta de caso antes de esa declaración. Hasta ahora no vi el punto de permitir eso, pero en este caso particular es útil ocultar el interruptor detrás de una macro agradable.


El flujo de código en sí ya es un olor de código que está sucediendo mucho en la función. Si no hay una solución directa a eso (la función es una función de verificación general), entonces usar RAII para que pueda regresar en lugar de saltar a una sección final de la función podría ser mejor.


Este es un problema bien conocido y resuelto desde una perspectiva de programación funcional, tal vez la mónada.

En respuesta al comentario que recibí a continuación, he editado mi introducción aquí: puede encontrar detalles completos sobre la implementación de mónadas C++ en varios lugares que le permitirán lograr lo que sugiere Rotsor. Se necesita un tiempo para asimilar las mónadas, así que, en lugar de eso, voy a sugerir aquí un rápido mecanismo tipo mónada "pobre" para el que solo necesita saber algo más que boost :: opcional.

Configure sus pasos de cálculo de la siguiente manera:

boost::optional<EnabledContext> enabled(boost::optional<Context> context); boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);

Obviamente, cada paso computacional puede hacer algo como retorno boost::none si el opcional que se le dio está vacío. Así por ejemplo:

struct Context { std::string coordinates_filename; /* ... */ }; struct EnabledContext { int x; int y; int z; /* ... */ }; boost::optional<EnabledContext> enabled(boost::optional<Context> c) { if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered. EnabledContext ec; std::ifstream file_in((*c).coordinates_filename.c_str()); file_in >> ec.x >> ec.y >> ec.z; return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value. }

Luego encadéalos juntos:

Context context("planet_surface.txt", ...); // Close over all needed bits and pieces boost::optional<EnergisedContext> result(energised(enabled(context))); if (result) { // A single level "if" statement // do work on *result } else { // error }

Lo bueno de esto es que puede escribir pruebas unitarias claramente definidas para cada paso computacional. Además, la invocación se lee como en inglés simple (como suele ser el caso con el estilo funcional).

Si no te importa la inmutabilidad y es más conveniente devolver el mismo objeto cada vez que puedas encontrar alguna variación usando shared_ptr o similar.


Estoy agregando una respuesta por el bien de la integridad. Otras respuestas indicaron que el bloque de condición grande podría dividirse en una función separada. Pero como también se señaló varias veces, este enfoque separa el código condicional del contexto original. Esta es una de las razones por las que se agregaron lambdas al lenguaje en C ++ 11. El uso de lambdas fue sugerido por otros, pero no se proporcionó una muestra explícita. He puesto uno en esta respuesta. Lo que me sorprende es que se siente muy similar al enfoque do { } while(0) de muchas maneras, y tal vez eso signifique que todavía es un goto disfrazado ...

earlier operations ... [&]()->void { if (!check()) return; ... ... if (!check()) return; ... ... if (!check()) return; ... ... }(); later operations


Hay ocasiones en que usar goto es en realidad la respuesta CORRECTA, al menos para aquellos que no se han criado en la creencia religiosa de que " goto nunca puede ser la respuesta, sin importar cuál sea la pregunta", y este es uno de esos casos.

Este código está usando el hack de do { ... } while(0); con el único propósito de vestir un goto como un break . Si vas a usar goto , entonces sé abierto al respecto. No tiene sentido que el código sea MÁS DIFÍCIL para leer.

Una situación particular es cuando tienes un montón de código con condiciones bastante complejas:

void func() { setup of lots of stuff ... if (condition) { ... ... if (!other condition) { ... if (another condition) { ... if (yet another condition) { ... if (...) ... } } } .... } finish up. }

En realidad, puede hacer que sea CLARO que el código sea correcto al no tener una lógica tan compleja.

void func() { setup of lots of stuff ... if (!condition) { goto finish; } ... ... if (other condition) { goto finish; } ... if (!another condition) { goto finish; } ... if (!yet another condition) { goto finish; } ... .... if (...) ... // No need to use goto here. finish: finish up. }

Edición: para aclarar, de ninguna manera estoy proponiendo el uso de goto como una solución general. Pero hay casos en los que goto es una mejor solución que otras soluciones.

Por ejemplo, imagine que estamos recopilando algunos datos, y las diferentes condiciones que se están probando son una especie de "este es el final de los datos que se recopilan", lo que depende de algún tipo de marcadores de "continuar / finalizar" que varían según el lugar estás en el flujo de datos

Ahora, cuando hayamos terminado, debemos guardar los datos en un archivo.

Y sí, a menudo hay otras soluciones que pueden proporcionar una solución razonable, pero no siempre.


No estoy particularmente en la forma de usar break o return en tal caso. Dado que normalmente cuando nos enfrentamos a una situación así, suele ser un método relativamente largo.

Si tenemos varios puntos de salida, puede causar dificultades cuando queremos saber qué es lo que hará que se ejecute cierta lógica. situación:

Por ejemplo,

if (conditionA) { .... if (conditionB) { .... if (conditionC) { myLogic(); } } }

Al observar los bloques myLogic() , es fácil descubrir que myLogic() solo ocurre cuando la conditionA and conditionB and conditionC myLogic() conditionA and conditionB and conditionC myLogic() son verdaderas.

Se vuelve mucho menos visible cuando hay rendimientos tempranos:

if (conditionA) { .... if (!conditionB) { return; } if (!conditionD) { return; } if (conditionC) { myLogic(); } }

Ya no podemos navegar hacia arriba desde myLogic() , mirando el bloque adjunto para descubrir la condición.

Hay diferentes soluciones que he usado. Aqui esta uno de ellos:

if (conditionA) { isA = true; .... } if (isA && conditionB) { isB = true; ... } if (isB && conditionC) { isC = true; myLogic(); }

(Por supuesto, es bienvenido usar la misma variable para reemplazar a todos isA isB isC).

Tal enfoque al menos le dará al lector de código, que myLogic()se ejecuta cuando isB && conditionC. Al lector se le da una pista de que necesita buscar más a fondo lo que hará que isB sea cierto.


Para mí do{...}while(0) está bien. Si no desea ver el do{...}while(0) , puede definir palabras clave alternativas para ellos.

Ejemplo:

//--------SomeUtilities.hpp--------- #define BEGIN_TEST do{ #define END_TEST }while(0); //--------SomeSourceFile.cpp-------- BEGIN_TEST if(!condition1) break; if(!condition2) break; if(!condition3) break; if(!condition4) break; if(!condition5) break; //processing code here END_TEST

Creo que el compilador eliminará la condición innecesaria while(0) en do{...}while(0) en versión binaria y convertirá los saltos en salto incondicional. Puede verificar su versión en lenguaje ensamblador para estar seguro.

El uso de goto también produce un código más limpio y es sencillo con la lógica de condición y salto. Puedes hacer lo siguiente:

{ if(!condition1) goto end_blahblah; if(!condition2) goto end_blahblah; if(!condition3) goto end_blahblah; if(!condition4) goto end_blahblah; if(!condition5) goto end_blahblah; //processing code here }end_blah_blah:; //use appropriate label here to describe... // ...the whole code inside the block.

Tenga en cuenta que la etiqueta se coloca después del cierre } . Este es el evitar un posible problema en goto que accidentalmente está colocando un código entre porque no vio la etiqueta. Ahora es como do{...}while(0) sin código de condición.

Para hacer este código más limpio y más comprensible, puedes hacer esto:

//--------SomeUtilities.hpp--------- #define BEGIN_TEST { #define END_TEST(_test_label_) }_test_label_:; #define FAILED(_test_label_) goto _test_label_ //--------SomeSourceFile.cpp-------- BEGIN_TEST if(!condition1) FAILED(NormalizeData); if(!condition2) FAILED(NormalizeData); if(!condition3) FAILED(NormalizeData); if(!condition4) FAILED(NormalizeData); if(!condition5) FAILED(NormalizeData); END_TEST(NormalizeData)

Con esto, puede hacer bloques anidados y especificar dónde desea salir / saltar.

//--------SomeUtilities.hpp--------- #define BEGIN_TEST { #define END_TEST(_test_label_) }_test_label_:; #define FAILED(_test_label_) goto _test_label_ //--------SomeSourceFile.cpp-------- BEGIN_TEST if(!condition1) FAILED(NormalizeData); if(!condition2) FAILED(NormalizeData); BEGIN_TEST if(!conditionAA) FAILED(DecryptBlah); if(!conditionBB) FAILED(NormalizeData); //Jump out to the outmost block if(!conditionCC) FAILED(DecryptBlah); // --We can now decrypt and do other stuffs. END_TEST(DecryptBlah) if(!condition3) FAILED(NormalizeData); if(!condition4) FAILED(NormalizeData); // --other code here BEGIN_TEST if(!conditionA) FAILED(TrimSpaces); if(!conditionB) FAILED(TrimSpaces); if(!conditionC) FAILED(NormalizeData); //Jump out to the outmost block if(!conditionD) FAILED(TrimSpaces); // --We can now trim completely or do other stuffs. END_TEST(TrimSpaces) // --Other code here... if(!condition5) FAILED(NormalizeData); //Ok, we got here. We can now process what we need to process. END_TEST(NormalizeData)

El código de Spaghetti no es culpa de goto , es culpa del programador. Aún puedes producir código espagueti sin usar goto .


Puedes usar un patrón de continuación simple con una variable bool :

bool goOn; if ((goOn = check0())) { ... } if (goOn && (goOn = check1())) { ... } if (goOn && (goOn = check2())) { ... } if (goOn && (goOn = check3())) { ... }

Esta cadena de ejecución se detendrá tan pronto como checkN devuelva un false . No se realizarán más check...() llamadas se realizarían debido a un cortocircuito del operador de && . Además, la optimización de los compiladores es lo suficientemente inteligente como para reconocer que establecer goOn en false es una calle de una sola vía, e inserta el goto end falta. Como resultado, el rendimiento del código anterior sería idéntico al de un do / while(0) , solo que sin un doloroso golpe a su legibilidad.


Recomendaría un enfoque similar a la respuesta de Mats menos el goto innecesario. Solo pon la lógica condicional en la función. Cualquier código que se ejecute siempre debe ir antes o después de invocar la función en el llamador:

void main() { //do stuff always func(); //do other stuff always } void func() { if (!condition) return; ... if (!other condition) return; ... if (!another condition) return; ... if (!yet another condition) return; ... }


Se considera una práctica aceptable aislar estas decisiones en una función y usar return s en lugar de break s. Si bien todas estas comprobaciones corresponden al mismo nivel de abstracción que la función, es un enfoque bastante lógico.

Por ejemplo:

void foo(...) { if (!condition) { return; } ... if (!other condition) { return; } ... if (!another condition) { return; } ... if (!yet another condition) { return; } ... // Some unconditional stuff }


Si no necesita introducir variables locales durante la ejecución, a menudo puede aplanar esto:

if (check()) { doStuff(); } if (stillOk()) { doMoreStuff(); } if (amIStillReallyOk()) { doEvenMore(); } // edit doThingsAtEndAndReportErrorStatus()


Similar a la respuesta de dasblinkenlight, pero evita la asignación dentro de la if un revisor de código podría "arreglarlo":

bool goOn = check0(); if (goOn) { ... goOn = check1(); } if (goOn) { ... goOn = check2(); } if (goOn) { ... }

...

Utilizo este patrón cuando es necesario verificar los resultados de un paso antes del siguiente, lo que difiere de una situación en la que todos los chequeos podrían hacerse por adelantado con un gran tipo if( check1() && check2()... .


Utilizar excepciones. Su código se verá mucho más limpio (y las excepciones se crearon exactamente para manejar los errores en el flujo de ejecución de un programa). Para limpiar recursos (descriptores de archivos, conexiones de base de datos, etc.), lea el artículo ¿Por qué C ++ no proporciona una construcción "finalmente"? .

#include <iostream> #include <stdexcept> // For exception, runtime_error, out_of_range int main () { try { if (!condition) throw std::runtime_error("nope."); ... if (!other condition) throw std::runtime_error("nope again."); ... if (!another condition) throw std::runtime_error("told you."); ... if (!yet another condition) throw std::runtime_error("OK, just forget it..."); } catch (std::runtime_error &e) { std::cout << e.what() << std::endl; } catch (...) { std::cout << "Caught an unknown exception/n"; } return 0; }


¿Por qué no se respondió el método de marcado que se utiliza desde las edades.

//you can use something like this (pseudocode) long var = 0; if(condition) flag a bit in var if(condition) flag another bit in var if(condition) flag another bit in var ............ if(var == certain number) { Do the required task }


TLDR : RAII , código transaccional (solo establece resultados o devuelve datos cuando ya está computado) y excepciones.

Respuesta larga:

En C , la mejor práctica para este tipo de código es agregar una etiqueta SALIR / LIMPIAR / otra en el código, donde se realiza la limpieza de los recursos locales y se devuelve un código de error (si lo hubiera). Esta es la mejor práctica porque divide el código naturalmente en inicialización, cálculo, confirmación y devolución:

error_code_type c_to_refactor(result_type *r) { error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere some_resource r1, r2; // , ...; if(error_ok != (result = computation1(&r1))) // Allocates local resources goto cleanup; if(error_ok != (result = computation2(&r2))) // Allocates local resources goto cleanup; // ... // Commit code: all operations succeeded *r = computed_value_n; cleanup: free_resource1(r1); free_resource2(r2); return result; }

En C, en la mayoría de las bases de código, el if(error_ok != ... y goto code usualmente está oculto detrás de algunas macros convenientes ( RET(computation_result) , ENSURE_SUCCESS(computation_result, return_code) , etc.).

C ++ ofrece herramientas adicionales sobre C :

  • La funcionalidad del bloque de limpieza se puede implementar como RAII, lo que significa que ya no necesita el bloque de cleanup completo y permite que el código del cliente agregue declaraciones de devolución anticipada.

  • Usted lanza cuando no puede continuar, transformando todo el if(error_ok != ... en llamadas directas).

Código de C ++ equivalente:

result_type cpp_code() { raii_resource1 r1 = computation1(); raii_resource2 r2 = computation2(); // ... return computed_value_n; }

Esta es la mejor práctica porque:

  • Es explícito (es decir, mientras que el manejo de errores no es explícito, el flujo principal del algoritmo es)

  • Es sencillo escribir código de cliente

  • Es minimo

  • Es simple

  • No tiene construcciones de código repetitivas.

  • No utiliza macros.

  • No usa we do { ... } while(0) construye

  • Es reutilizable con un esfuerzo mínimo (es decir, si quiero copiar la llamada a computation2(); a una función diferente, no tengo que asegurarme de agregar un do { ... } while(0) en el código nuevo, ni #define una macro de envoltura goto y una etiqueta de limpieza, ni ninguna otra cosa).


Otro patrón útil si necesita diferentes pasos de limpieza dependiendo de dónde está la falla:

private ResultCode DoEverything() { ResultCode processResult = ResultCode.FAILURE; if (DoStep1() != ResultCode.SUCCESSFUL) { Step1FailureCleanup(); } else if (DoStep2() != ResultCode.SUCCESSFUL) { Step2FailureCleanup(); processResult = ResultCode.SPECIFIC_FAILURE; } else if (DoStep3() != ResultCode.SUCCESSFUL) { Step3FailureCleanup(); } ... else { processResult = ResultCode.SUCCESSFUL; } return processResult; }


Si utiliza el mismo controlador de errores para todos los errores, y cada paso devuelve un error que indica que es correcto:

if( DoSomething() && DoSomethingElse() && DoAThirdThing() ) { // do good condition action } else { // handle error }

(Similar a la respuesta de tyzoid, pero las condiciones son las acciones y el && evita que ocurran acciones adicionales después del primer fallo)


Consolidar en una ifdeclaración:

if( condition && other_condition && another_condition && yet_another_condition && ... ) { if (final_cond){ //Do stuff } else { //Do other stuff } }

Este es el patrón utilizado en lenguajes como Java donde se eliminó la palabra clave goto.


Me sorprende la cantidad de respuestas diferentes que se presentan aquí. Pero, finalmente, en el código que tengo que cambiar (es decir, eliminar este do-while(0)truco o cualquier otra cosa), hice algo diferente de cualquiera de las respuestas que se mencionan aquí y estoy confundido por qué nadie pensó esto. Esto es lo que hice:

Código inicial:

do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0); finishingUpStuff.

Ahora:

finish(params) { ... ... } if(!check()){ finish(params); return; } ... ... if(!check()){ finish(params); return; } ... ... if(!check()){ finish(params); return; } ... ...

Entonces, lo que se ha hecho aquí es que las cosas de acabado se han aislado en una función y, de repente, ¡se han vuelto tan simples y limpias!

Pensé que valía la pena mencionar esta solución, así que la proporcioné aquí.


No soy programador de C++ , por lo que no escribiré ningún código aquí, pero hasta ahora nadie ha mencionado una solución orientada a objetos. Así que aquí está mi conjetura sobre eso:

Tener una interfaz genérica que proporcione un método para evaluar una sola condición. Ahora puede usar una lista de implementaciones de esas condiciones en su objeto que contiene el método en cuestión. Repite la lista y evalúa cada condición, posiblemente rompiéndose antes si falla una.

Lo bueno es que dicho diseño se adhiere muy bien al principio abierto / cerrado , porque puede agregar fácilmente nuevas condiciones durante la inicialización del objeto que contiene el método en cuestión. Incluso puede agregar un segundo método a la interfaz con el método para la evaluación de la condición y devolver una descripción de la condición. Esto puede ser usado para sistemas de auto-documentación.

La desventaja, sin embargo, es que hay un poco más de sobrecarga debido al uso de más objetos y la iteración en la lista.


Primero, un breve ejemplo para mostrar por qué gotono es una buena solución para C ++:

struct Bar { Bar(); }; extern bool check(); void foo() { if (!check()) goto out; Bar x; out: }

Intente compilar esto en un archivo de objeto y vea qué sucede. A continuación, intente el equivalente do+ break+ while(0).

Eso fue un aparte. El punto principal sigue.

Esos pequeños trozos de código a menudo requieren algún tipo de limpieza en caso de que falle la función completa. Esas limpiezas generalmente quieren que se realicen en el orden opuesto a los trozos en sí, ya que "desenrolla" el cálculo parcialmente terminado.

Una opción para obtener estas semánticas es RAII ; Ver la respuesta de @utnapistim. C ++ garantiza que los destructores automáticos se ejecuten en el orden opuesto a los constructores, lo que naturalmente proporciona un "desenrollado".

Pero eso requiere muchas clases de RAII. A veces, una opción más simple es simplemente usar la pila:

bool calc1() { if (!check()) return false; // ... Do stuff1 here ... if (!calc2()) { // ... Undo stuff1 here ... return false; } return true; } bool calc2() { if (!check()) return false; // ... Do stuff2 here ... if (!calc3()) { // ... Undo stuff2 here ... return false; } return true; }

...y así. Esto es fácil de auditar, ya que coloca el código de "deshacer" al lado del código de "hacer". La auditoría fácil es buena. También hace que el flujo de control sea muy claro. Es un patrón útil para C, también.

Puede requerir que las calcfunciones tomen muchos argumentos, pero eso no suele ser un problema si sus clases / estructuras tienen buena cohesión. (Es decir, las cosas que pertenecen juntas viven en un solo objeto, por lo que estas funciones pueden llevar punteros o referencias a una pequeña cantidad de objetos y aún hacer un montón de trabajo útil)



typedef bool (*Checker)(); Checker * checkers[]={ &checker0,&checker1,.....,&checkerN,NULL }; bool checker1(){ if(condition){ ..... ..... return true; } return false; } bool checker2(){ if(condition){ ..... ..... return true; } return false; } ...... void doCheck(){ Checker ** checker = checkers; while( *checker && (*checker)()) checker++; }

¿Qué hay sobre eso?