c++ design goto

c++ - Use GOTO o no?



design (30)

Básicamente hay dos puntos que las personas están haciendo con respecto a los gotos y su código:

  1. Goto es malo. Es muy raro encontrar un lugar donde necesites gotos, pero no recomendaría golpearlo por completo. Aunque C ++ tiene un flujo de control lo suficientemente inteligente para hacer goto raramente apropiado.

  2. Su mecanismo de limpieza es incorrecto: este punto es mucho más importante. En C, usar la administración de la memoria por su cuenta no solo está bien, sino que a menudo es la mejor manera de hacer las cosas. En C ++, su objetivo debe ser evitar la administración de memoria tanto como sea posible. Debe evitar la administración de memoria tanto como sea posible. Deje que el compilador lo haga por usted. En lugar de usar new , solo declara las variables. La única vez que realmente necesitará administración de memoria es cuando no conoce el tamaño de sus datos por adelantado. Incluso entonces, debería intentar usar algunas de las colecciones de STL .

En el caso de que necesites legítimamente gestión de memoria (en realidad no has proporcionado ninguna prueba de esto), entonces debes encapsular tu gestión de memoria dentro de una clase a través de constructores para asignar memoria y deconstructores para desasignar la memoria.

Su respuesta de que su forma de hacer las cosas es mucho más fácil no es realmente cierto a la larga. En primer lugar, una vez que te sientas bien con C ++, estos constructores serán una segunda naturaleza. Personalmente, encuentro que usar constructores es más fácil que utilizar el código de limpieza, ya que no tengo que prestar mucha atención para asegurarme de que estoy desasignando correctamente. En cambio, puedo dejar que el objeto salga del alcance y el lenguaje lo maneje por mí. Además, mantenerlos es MUCHO más fácil que mantener una sección de limpieza y mucho menos propenso a problemas.

En resumen, goto puede ser una buena opción en algunas situaciones, pero no en esta. Aquí solo hay flojera a corto plazo.

Actualmente estoy trabajando en un proyecto donde las declaraciones goto se usan ampliamente. El propósito principal de las declaraciones goto es tener una sección de limpieza en una rutina en lugar de declaraciones de devolución múltiples. Como abajo:

BOOL foo() { BOOL bRetVal = FALSE; int *p = NULL; p = new int; if (p == NULL) { cout<<" OOM /n"; goto Exit; } // Lot of code... Exit: if(p) { delete p; p = NULL; } return bRetVal; }

Esto lo hace mucho más fácil ya que podemos rastrear nuestro código de limpieza en una sección del código, es decir, después de la etiqueta de Salida.

Sin embargo, he leído en muchos lugares que es una mala práctica tener declaraciones goto.

Actualmente estoy leyendo el libro Code Complete y dice que necesitamos usar variables cercanas a sus declaraciones. Si usamos goto, entonces necesitamos declarar / inicializar todas las variables antes del primer uso de goto; de lo contrario, el compilador dará errores de que la inicialización de la variable xx es omitida por la instrucción goto.

¿Cuál es el camino correcto?

Del comentario de Scott:

Parece que usar goto para saltar de una sección a otra es malo, ya que hace que el código sea difícil de leer y comprender.

Pero si usamos goto solo para avanzar y una etiqueta, entonces debería estar bien (?).


Creo que otras respuestas (y sus comentarios) han cubierto todos los puntos importantes, pero hay algo que aún no se ha hecho correctamente:

Cómo debería ser el código en su lugar:

bool foo() //lowercase bool is a built-in C++ type. Use it if you''re writing C++. { try { std::unique_ptr<int> p(new int); // lots of code, and just return true or false directly when you''re done } catch (std::bad_alloc){ // new throws an exception on OOM, it doesn''t return NULL cout<<" OOM /n"; return false; } }

Bueno, es más corto, y hasta donde puedo ver, es más correcto (maneja el caso OOM correctamente), y lo más importante, no tuve que escribir ningún código de limpieza ni hacer nada especial para "asegurarme de que mi valor de retorno esté inicializado". ".

Un problema con su código que realmente solo noté cuando escribí esto, es "¿qué demonios es el valor de bRetVal en este punto?". No sé porque fue declarado waaaaay arriba, y fue asignado por última vez a cuándo? En algún momento por encima de esto. Tengo que leer toda la función para asegurarme de que entiendo lo que se va a devolver.

¿Y cómo me convenzo de que la memoria se libera?

¿Cómo que nunca olvidamos saltar a la etiqueta de limpieza? Tengo que trabajar hacia atrás desde la etiqueta de limpieza, encontrar cada giro que lo señala y, lo que es más importante, encontrar los que no están allí. Necesito seguir todas las rutas de la función solo para asegurarme de que la función se limpia correctamente. Eso me parece un código de spaghetti.

Código muy frágil, porque cada vez que un recurso tiene que ser limpiado, debe recordar duplicar su código de limpieza. ¿Por qué no escribirlo una vez, en el tipo que debe limpiarse? ¿Y luego confiar en que se ejecuta automáticamente, cada vez que lo necesitamos?



Debería leer este resumen de hilos de las listas de distribución del kernel de Linux (prestando especial atención a las respuestas de Linus Torvalds) antes de formar una política para goto :

http://kerneltrap.org/node/553/2131


El código que nos está dando es (casi) código C escrito dentro de un archivo C ++. El tipo de limpieza de memoria que está utilizando estaría bien en un programa en C que no utiliza códigos / bibliotecas C ++.

En C ++, su código es simplemente inseguro y poco confiable. En C ++, el tipo de gestión que está solicitando se realiza de forma diferente. Usa constructores / destructores. Use punteros inteligentes. Usa la pila. En una palabra, use RAII .

Su código podría (es decir, en C ++, DEBERÍA) escribirse como:

BOOL foo() { BOOL bRetVal = FALSE; std::auto_ptr<int> p = new int; // Lot of code... return bRetVal ; }

(Tenga en cuenta que new-ing an int es algo tonto en código real, pero puede reemplazar int por cualquier tipo de objeto, y luego, tiene más sentido). Imaginemos que tenemos un objeto de tipo T (T podría ser una int, alguna clase de C ++, etc.). Entonces el código se convierte en:

BOOL foo() { BOOL bRetVal = FALSE; std::auto_ptr<T> p = new T; // Lot of code... return bRetVal ; }

O incluso mejor, usando la pila:

BOOL foo() { BOOL bRetVal = FALSE; T p ; // Lot of code... return bRetVal; }

De todos modos, cualquiera de los ejemplos anteriores son magnitudes más fáciles de leer y seguras que su ejemplo.

RAII tiene muchas facetas (es decir, usar punteros inteligentes, la pila, usar vectores en lugar de matrices de longitud variable, etc.), pero en general se trata de escribir el menor código posible, dejando que el compilador limpie las cosas en el momento correcto.


En general, debe diseñar sus programas para limitar la necesidad de gotos. Use técnicas OO para "limpiar" sus valores devueltos. Hay formas de hacer esto que no requieren el uso de gotos o complicar el código. Hay casos en que los gotos son muy útiles (por ejemplo, ámbitos profundamente anidados), pero si es posible, se deben evitar.


En general, y en la superficie, no hay nada malo con su enfoque, siempre que solo tenga una etiqueta, y que los gotos siempre avancen. Por ejemplo, este código:

int foo() { int *pWhatEver = ...; if (something(pWhatEver)) { delete pWhatEver; return 1; } else { delete pWhatEver; return 5; } }

Y este código:

int foo() { int ret; int *pWhatEver = ...; if (something(pWhatEver)) { ret = 1; goto exit; } else { ret = 1; goto exit; } exit: delete pWhatEver; return ret; }

realmente no son tan diferentes el uno del otro. Si puedes aceptar uno, deberías poder aceptar el otro.

Sin embargo, en muchos casos, el patrón RAII (adquisición de recursos es la inicialización) puede hacer que el código sea mucho más limpio y más fácil de mantener. Por ejemplo, este código:

int foo() { Auto<int> pWhatEver = ...; if (something(pWhatEver)) { return 1; } else { return 5; } }

es más corto, más fácil de leer y más fácil de mantener que los dos ejemplos anteriores.

Entonces, recomendaría usar el enfoque RAII si puede.


En los ocho años que he estado programando he usado mucho, la mayoría fue en el primer año cuando estaba usando una versión de GW-BASIC y un libro de 1980 que no dejaba claro que solo debería ser utilizado en ciertos casos. La única vez que uso goto en C ++ es cuando tenía un código como el siguiente, y no estoy seguro de si había una forma mejor.

for (int i=0; i<10; i++) { for (int j=0; j<10; j++) { if (somecondition==true) { goto finish; } //Some code } //Some code } finish:

La única situación que conozco en donde goto todavía se usa mucho es el lenguaje ensamblador de mainframe, y los programadores que conozco se aseguran de documentar dónde está saltando el código y por qué.


Goto proporciona mejor no se repita (DRY) cuando "tail-end-logic" es común para algunos-pero-no-todos-los casos. Especialmente dentro de una declaración de "cambio", a menudo uso goto cuando algunas de las ramas de cambio tienen un extremo común.

switch(){ case a: ... goto L_abTail; case b: ... goto L_abTail; L_abTail: <commmon stuff> break://end of case b case c: ..... }//switch

Probablemente se haya dado cuenta de que la introducción de llaves adicionales es suficiente para satisfacer al compilador cuando necesita una fusión de extremo a extremo en el medio de una rutina. En otras palabras, no es necesario que declares todo en la parte superior; eso es legibilidad inferior de hecho.

... goto L_skipMiddle; { int declInMiddleVar = 0; .... } L_skipMiddle: ;

Con las últimas versiones de Visual Studio detectando el uso de variables no inicializadas, siempre estoy inicializando la mayoría de las variables, aunque creo que pueden asignarse en todas las ramas: es fácil codificar una instrucción de "rastreo" que hace referencia a una variable que nunca se asignó porque su mente no piensa en el enunciado de seguimiento como "código real", pero por supuesto Visual Studio aún detectará un error.

Además de no repetirse, la asignación de nombres de etiqueta a esa lógica de cola final parece ayudar a mi mente a mantener las cosas en orden eligiendo buenos nombres de etiqueta. Sin una etiqueta con sentido, tus comentarios podrían terminar diciendo lo mismo.

Por supuesto, si en realidad está asignando recursos, entonces si el auto-ptr no encaja, realmente debe usar un try-catch, pero tail-end-merge-don''t-repeat-yourself no ocurre con frecuencia cuando se usa la excepción-seguridad. no es un problema.

En resumen, mientras que goto puede usarse para codificar estructuras parecidas a spaghetti, en el caso de una secuencia final que es común a algunos casos pero no todos, entonces el goto MEJORA la legibilidad del código e incluso la capacidad de mantenimiento si de lo contrario estarías copiando / pegando cosas para que mucho más tarde alguien pueda actualizar uno-y-no-el-otro. Entonces, es otro caso en el que ser fanático sobre un dogma puede ser contraproducente.


La desventaja de GOTO es bastante discutida. Simplemente agregaría que 1) a veces debes usarlos y debes saber cómo minimizar los problemas, y 2) algunas técnicas de programación aceptadas son GOTO-en-disfraz, así que ten cuidado.

1) Cuando tenga que usar GOTO, como en ASM o en archivos .bat, piense como un compilador. Si quieres codificar

if (some_test){ ... the body ... }

hacer lo que hace un compilador Genera una etiqueta cuyo propósito es saltear el cuerpo, no hacer lo que sigue. es decir

if (not some_test) GOTO label_at_end_of_body ... the body ... label_at_end_of_body:

No

if (not some_test) GOTO the_label_named_for_whatever_gets_done_next ... the body ... the_label_named_for_whatever_gets_done_next:

En otras palabras, el propósito de la etiqueta no es hacer algo, sino omitir algo.

2) Lo que llamo GOTO-en-disfraz es cualquier cosa que pueda convertirse en el código GOTO + ETIQUETAS simplemente definiendo un par de macros. Un ejemplo es la técnica de implementación de autómatas de estado finito al tener una variable de estado y una instrucción while-switch.

while (not_done){ switch(state){ case S1: ... do stuff 1 ... state = S2; break; case S2: ... do stuff 2 ... state = S1; break; ......... } }

puede convertirse en:

while (not_done){ switch(state){ LABEL(S1): ... do stuff 1 ... GOTO(S2); LABEL(S2): ... do stuff 2 ... GOTO(S1); ......... } }

simplemente definiendo un par de macros. Casi cualquier FSA se puede convertir en código estructurado e inútil. Prefiero alejarme del código GOTO-en-disfraz porque puede entrar en los mismos problemas con el código de spaghetti que con los gotos no disimulados.

Agregado: Solo para tranquilizar: creo que una marca de un buen programador reconoce cuando las reglas comunes no se aplican.


Las dos únicas razones por las que utilizo goto en mi código C ++ son:

  • Rompiendo un bucle anidado de nivel 2+
  • Flujos complicados como este (un comentario en mi programa):

    /* Analysis algorithm: 1. if classData [exporter] [classDef with name ''className''] exists, return it, else 2. if project/target_codename/temp/classmeta/className.xml exist, parse it and go back to 1 as it will succeed. 3. if that file don''t exists, generate it via haxe -xml, and go back to 1 as it will succeed. */

Para la legibilidad del código aquí, después de este comentario, definí la etiqueta step1 y la utilicé en los pasos 2 y 3. En realidad, en más de 60 archivos fuente, solo esta situación y una de 4 niveles anidados son los lugares que utilicé goto. Solo dos lugares


Mucha gente enloquece con gotos son malvados; ellos no son. Dicho eso, nunca necesitarás uno; casi siempre hay una mejor manera.

Cuando me encuentro "necesitando" un goto para hacer este tipo de cosas, casi siempre encuentro que mi código es demasiado complejo y se puede dividir fácilmente en unas pocas llamadas a métodos que son más fáciles de leer y manejar. Su código de llamada puede hacer algo como:

// Setup if( methodA() && methodB() && methodC() ) // Cleanup

No es que esto sea perfecto, pero es mucho más fácil de seguir ya que todos sus métodos serán nombrados para indicar claramente cuál podría ser el problema.

Sin embargo, la lectura de los comentarios debe indicar que su equipo tiene problemas más acuciantes que la manipulación.


No estoy seguro de a qué se refiere con el código de limpieza, pero en C ++ hay un concepto llamado " la adquisición de recursos es la inicialización " y debería ser responsabilidad de los destructores limpiar las cosas.

(Tenga en cuenta que en C # y Java, esto generalmente se resuelve mediante try / finally)

Para obtener más información, consulte esta página: http://www.research.att.com/~bs/bs_faq2.html#finally

EDITAR : Déjame aclarar esto un poco.

Considera el siguiente código:

void MyMethod() { MyClass *myInstance = new MyClass("myParameter"); /* Your code here */ delete myInstance; }

El problema : ¿qué sucede si tienes múltiples salidas de la función? ¡Debe realizar un seguimiento de cada salida y eliminar sus objetos en todas las salidas posibles! De lo contrario, tendrá fugas de memoria y recursos de zombis, ¿verdad?

La solución : use referencias de objetos en su lugar, ya que se limpian automáticamente cuando el control deja el ámbito.

void MyMethod() { MyClass myInstance("myParameter"); /* Your code here */ /* You don''t need delete - myInstance will be destructed and deleted * automatically on function exit */ }

Ah, sí, y use std::unique_ptr o algo similar porque el ejemplo de arriba es obviamente imperfecto.


Nunca tuve que usar un goto en C ++. Nunca. NUNCA. Si hay una situación que debería usarse, es increíblemente raro. Si realmente está considerando hacer una parte estándar de su lógica, algo se ha salido de las pistas.



Su código es extremadamente no idiomático y nunca debe escribirlo. Básicamente estás emulando C en C ++. Pero otros han comentado sobre eso, y señalaron a RAII como la alternativa.

Sin embargo, su código no funcionará como esperaba, porque esto:

p = new int; if(p==NULL) { … }

nunca evaluará a true (excepto si has sobrecargado el operator new de una manera extraña). Si el operator new no puede asignar suficiente memoria, arroja una excepción, nunca , nunca devuelve 0 , al menos no con este conjunto de parámetros; hay una ubicación especial: nueva sobrecarga que toma una instancia de tipo std::nothrow y que de hecho devuelve 0 lugar de arrojar una excepción. Pero esta versión rara vez se usa en el código normal. Algunos códigos de bajo nivel o aplicaciones de dispositivos integrados podrían beneficiarse en contextos en los que tratar con excepciones sea demasiado costoso.

Algo similar es cierto para su bloque de delete , como Harald dijo: if (p) es innecesario delante de delete p .

Además, no estoy seguro de si su ejemplo fue elegido intencionalmente porque este código se puede reescribir de la siguiente manera:

bool foo() // prefer native types to BOOL, if possible { bool ret = false; int i; // Lots of code. return ret; }


Tal como se usa en el kernel de Linux, los goto utilizados para la limpieza funcionan bien cuando una sola función debe realizar 2 o más pasos que pueden necesitar deshacerse. Los pasos no necesitan ser asignación de memoria. Puede ser un cambio de configuración en un fragmento de código o en un registro de un conjunto de chips de E / S. Los de Goto solo deberían ser necesarios en un pequeño número de casos, pero a menudo, cuando se usan correctamente, pueden ser la mejor solución. Ellos no son malvados Ellos son una herramienta.

En lugar de...

do_step1; if (failed) { undo_step1; return failure; } do_step2; if (failed) { undo_step2; undo_step1; return failure; } do_step3; if (failed) { undo_step3; undo_step2; undo_step1; return failure; } return success;

usted puede hacer lo mismo con declaraciones goto como esta:

do_step1; if (failed) goto unwind_step1; do_step2; if (failed) goto unwind_step2; do_step3; if (failed) goto unwind_step3; return success; unwind_step3: undo_step3; unwind_step2: undo_step2; unwind_step1: undo_step1; return failure;

Debe quedar claro que dados estos dos ejemplos, uno es preferible al otro. En cuanto a la multitud de RAII ... No hay nada de malo en ese enfoque, siempre y cuando puedan garantizar que la anulación siempre ocurra exactamente en orden inverso: 3, 2, 1. Y, por último, algunas personas no usan excepciones en su código e instruir a los compiladores para deshabilitarlos. Por lo tanto, no todos los códigos deben ser excepcionales.


Todo el propósito de la expresión idiomática de C-función-tiene-un-punto de salida fue poner todo el material de limpieza en un solo lugar. Si utiliza destructores de C ++ para gestionar la limpieza, eso ya no es necesario; la limpieza se realizará independientemente de la cantidad de puntos de salida que tenga una función. Entonces, en el código C ++ diseñado correctamente, ya no hay necesidad de este tipo de cosas.


Todo lo anterior es válido, es posible que también desee ver si puede reducir la complejidad de su código y aliviar la necesidad de ir reduciendo la cantidad de código que está en la sección marcada como "gran cantidad de código". en tu ejemplo. Additionaly delete 0 es una sentencia válida de C ++


Tu ejemplo no es una excepción segura.

Si está utilizando goto para limpiar el código, entonces, si ocurre una excepción antes del código de limpieza, se perderá por completo. Si afirma que no utiliza excepciones, se equivoca porque el new arrojará bad_alloc cuando no tiene suficiente memoria.

También en este punto (cuando se lanza bad_alloc), su pila se desenrollará, faltando todo el código de limpieza en cada función en el camino hacia arriba de la pila de llamadas, por lo tanto no limpiando su código.

Debe buscar investigar sobre indicadores inteligentes. En la situación anterior, puede usar un std::auto_ptr<> .

También tenga en cuenta que en el código C ++ no es necesario verificar si un puntero es NULL (generalmente porque nunca tiene punteros RAW), sino porque new no devolverá NULL (arroja).

También en C ++ a diferencia de (C) es común ver retornos tempranos en el código. Esto se debe a que RAII hará la limpieza automáticamente, mientras que en el código C, debe asegurarse de agregar un código de limpieza especial al final de la función (un poco como su código).


Usar goto para ir a una sección de limpieza va a causar muchos problemas.

Primero, las secciones de limpieza son propensas a problemas. Tienen baja cohesión (no hay un rol real que pueda describirse en términos de lo que el programa intenta hacer), alto acoplamiento (la corrección depende en gran medida de otras secciones del código) y no son para nada a prueba de excepciones. Vea si puede usar destructores para la limpieza. Por ejemplo, si int *p cambia a auto_ptr<int> p , lo que p señala se liberará automáticamente.

En segundo lugar, como usted señala, va a obligarlo a declarar variables mucho antes de su uso, lo que dificultará la comprensión del código.

En tercer lugar, mientras propones un uso bastante disciplinado de goto, habrá la tentación de usarlos de una manera más flexible, y entonces el código será difícil de entender.

Hay muy pocas situaciones donde un goto es apropiado. La mayoría de las veces, cuando está tentado de usarlos, es una señal de que está haciendo las cosas mal.


Usar las etiquetas GOTO en C ++ es una mala forma de programar, puede reducir la necesidad haciendo la programación OO (deconstructors!) Y tratando de mantener los procedimientos lo más pequeños posible.

Su ejemplo se ve un poco raro, no hay necesidad de eliminar un puntero NULL . Y hoy en día se lanza una excepción cuando no se puede asignar un puntero.

Su procedimiento podría simplemente escribirse como:

bool foo() { bool bRetVal = false; int p = 0; // Calls to various methods that do algorithms on the p integer // and give a return value back to this procedure. return bRetVal; }

You should place a try catch block in the main program handling out of memory problems that informs the user about the lack of memory, which is very rare ... (Doesn''t the OS itself inform about this too?)

Also note that there is not always the need to use a pointer , they are only useful for dynamic things . (Creating one thing inside a method not depending on input from anywhere isn''t really dynamic)


A few years ago I came up with a pseudo-idiom that avoids goto, and is vaguely similar to doing exception handling in C. It has been probably already invented by someone else so I guess I "discovered it independently" :)

BOOL foo() { BOOL bRetVal = FALSE; int *p=NULL; do { p = new int; if(p==NULL) { cout<<" OOM /n"; break; } // Lot of code... bRetVal = TRUE; } while (false); if(p) { delete p; p= NULL; } return bRetVal; }


From all the previous comments:

  1. goto is very very bad
  2. It makes code hard to read and understand.
  3. It can cause the well-known issue "spaghetti code"
  4. Everyone agree that it should not be done.

But I am using in following scenario

  1. It''s used to go forward and only to one label.
  2. goto section is used to cleanup code and set a return value. If I don''t use goto then I need to create a class of every data type. Like I need to wrap int * into a class.
  3. It''s being followed in the whole project.

I agree that it''s bad, but still it''s making things much easier if followed properly.


I am not going to say that goto is always bad, but your use of it most certainly is. That kind of "cleanup sections" was pretty common in early 1990''s, but using it for new code is pure evil.


I may have missed something: you jump to the label Exit if P is null, then test to see if it''s not null (which it''s not) to see if you need to delete it (which isn''t necessary because it was never allocated in the first place).

The if/goto won''t, and doesn''t need to delete p. Replacing the goto with a return false would have the same effect (and then you could remove the Exit label).

The only places I know where goto''s are useful are buried deep in nasty parsers (or lexical analyzers), and in faking out state machines (buried in a mass of CPP macros). In those two cases they''ve been used to make very twisted logic simpler, but that is very rare.

Functions (A calls A''), Try/Catches and setjmp/longjmps are all nicer ways of avoiding a difficult syntax problem.

Paul.


I think using the goto for exit code is bad since there''s a lot of other solutions with low overhead such as having an exit function and returning the exit function value when needed. Typically in member functions though, this shouldn''t be needed, otherwise this could be indication that there''s a bit too much code bloat happening.

Typically, the only exception I make of the "no goto" rule when programming is when breaking out of nested loops to a specific level, which I''ve only ran into the need to do when working on mathematical programming.

Por ejemplo:

for(int i_index = start_index; i_index >= 0; --i_index) { for(int j_index = start_index; j_index >=0; --j_index) for(int k_index = start_index; k_index >= 0; --k_index) if(my_condition) goto BREAK_NESTED_LOOP_j_index; BREAK_NESTED_LOOP_j_index:; }


That code has a bunch of problems, most of which were pointed out already, for example:

  • The function is too long; refactoring out some code into separate functions might help.

  • Using pointers when normal instances will probably work just fine.

  • Not taking advantage of STL types such as auto_ptr

  • Incorrectly checking for errors, and not catching exceptions. (I would argue that checking for OOM is pointless on the vast majority of platforms, since if you run out of memory you have bigger problems than your software can fix, unless you are writing the OS itself)

I have never needed a goto, and I''ve always found that using goto is a symptom of a bigger set of problems. Your case appears to be no exception.


The easiest way to avoid what you are doing here is to put all of this cleanup into some kind of simple structure and create an instance of it. For example instead of:

void MyClass::myFunction() { A* a = new A; B* b = new B; C* c = new C; StartSomeBackgroundTask(); MaybeBeginAnUndoBlockToo(); if ( ... ) { goto Exit; } if ( ... ) { .. } else { ... // what happens if this throws an exception??? too bad... goto Exit; } Exit: delete a; delete b; delete c; StopMyBackgroundTask(); EndMyUndoBlock(); }

you should rather do this cleanup in some way like:

struct MyFunctionResourceGuard { MyFunctionResourceGuard( MyClass& owner ) : m_owner( owner ) , _a( new A ) , _b( new B ) , _c( new C ) { m_owner.StartSomeBackgroundTask(); m_owner.MaybeBeginAnUndoBlockToo(); } ~MyFunctionResourceGuard() { m_owner.StopMyBackgroundTask(); m_owner.EndMyUndoBlock(); } std::auto_ptr<A> _a; std::auto_ptr<B> _b; std::auto_ptr<C> _c; }; void MyClass::myFunction() { MyFunctionResourceGuard guard( *this ); if ( ... ) { return; } if ( ... ) { .. } else { ... } }


Using "GOTO" will change the "logics" of a program and how you enterpret or how you would imagine it would work.

Avoiding GOTO-commands have always worked for me so guess when you think you might need it, all you maybe need is a re-design.

However, if we look at this on an Assmebly-level, jusing "jump" is like using GOTO and that''s used all the time, BUT, in Assembly you can clear out, what you know you have on the stack and other registers before you pass on.

So, when using GOTO, i''d make sure the software would "appear" as the co-coders would enterpret, GOTO will have an "bad" effect on your software imho.

So this is more an explenation to why not to use GOTO and not a solution for a replacement, because that is VERY much up to how everything else is built.