web-dev-qa-db-fra.com

Utilisez GOTO ou pas?

Actuellement, je travaille sur un projet dans lequel les déclarations goto sont extrêmement utilisées. Le but principal des instructions goto est d’avoir une section de nettoyage dans une routine plutôt que plusieurs instructions de retour . Comme ci-dessous:

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;
}

Cela facilite beaucoup la tâche, car nous pouvons suivre notre code de nettoyage dans une section du code, c'est-à-dire après l'étiquette de sortie.

Cependant, j'ai lu de nombreux endroits où il est mauvais d'avoir des déclarations.

Actuellement, je lis le livre Code Complete, qui indique que nous devons utiliser des variables proches de leurs déclarations. Si nous utilisons goto, nous devons déclarer/initialiser toutes les variables avant la première utilisation de goto, sinon le compilateur générera des erreurs si l'initialisation de la variable xx est ignorée par l'instruction goto.

Quel est le bon chemin?


Du commentaire de Scott:

Il semble que l'utilisation de goto pour passer d'une section à l'autre est une mauvaise chose car cela rend le code difficile à lire et à comprendre.

Mais si nous utilisons goto juste pour aller de l'avant et pour un label, alors ça devrait aller (?).

55
anand

Je ne suis pas sûr de comprendre ce que vous entendez par code de nettoyage, mais en C++, il existe un concept appelé "l'acquisition de ressource est une initialisation}" et il devrait incomber à vos destructeurs de nettoyer les éléments.

(Notez qu'en C # et Java, ceci est généralement résolu par try/finally)

Pour plus d'informations, consultez cette page: http://www.research.att.com/~bs/bs_faq2.html#finally

EDIT: Laissez-moi clarifier un peu cela.

Considérons le code suivant:

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

Le problème: Que se passe-t-il si vous avez plusieurs exits de la fonction? Vous devez garder une trace de chaque sortie et supprimer vos objets à toutes les sorties possibles! Sinon, vous aurez des fuites de mémoire et des ressources zombies, non?

La solution: utilisez plutôt les références d'objet, car elles sont automatiquement nettoyées lorsque le contrôle quitte la portée.

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

Oh oui, et utilisez std::unique_ptr ou quelque chose de similaire, car l'exemple ci-dessus est évidemment imparfait.

58
Tamas Czinege

Je n'ai jamais eu à utiliser un goto en C++. Déjà. DÉJÀ. S'il y a une situation, il faut l'utiliser, c'est incroyablement rare. Si vous envisagez réellement de faire de la goto une partie intégrante de votre logique, quelque chose s’est envolé.

59
Gene Roberts

Il y a essentiellement deux choses que les gens soulèvent concernant les gotos et votre code: 

  1. Goto est mauvais. Il est très rare de rencontrer un endroit où vous avez besoin de gotos, mais je ne suggérerais pas de le supprimer complètement. Bien que C++ ait un flux de contrôle assez intelligent pour rendre goto rarement approprié. 

  2. Votre mécanisme de nettoyage est erroné: Ce point est bien plus important. En C, utiliser la gestion de mémoire par vous-même n’est pas seulement correct, mais constitue souvent la meilleure façon de procéder. En C++, votre objectif devrait être d'éviter autant que possible la gestion de la mémoire. Vous devez éviter autant que possible la gestion de la mémoire. Laissez le compilateur le faire pour vous. Plutôt que d'utiliser new, déclarez simplement les variables. La gestion de la mémoire est le seul cas où vous ne connaissez pas la taille de vos données à l'avance. Même dans ce cas, vous devriez plutôt essayer d’utiliser certaines des collections STL.

Si vous avez légitimement besoin d'une gestion de la mémoire (vous n'avez pas vraiment fourni de preuve à ce sujet), vous devez alors encapsuler votre gestion de la mémoire dans une classe via des constructeurs pour allouer de la mémoire et des déconstructeurs pour la désallouer.

Votre réponse selon laquelle votre façon de faire les choses est beaucoup plus facile n'est pas vraiment vraie à long terme. Premièrement, une fois que vous aurez une bonne idée du C++, faire de tels constructeurs sera une seconde nature. Personnellement, je trouve plus facile d'utiliser des constructeurs que d'utiliser du code de nettoyage, car je n'ai pas besoin de faire très attention pour m'assurer de désallouer correctement. Au lieu de cela, je peux simplement laisser l'objet quitter la portée et le langage le gère pour moi. En outre, leur maintenance est BEAUCOUP plus facile que de maintenir une section de nettoyage et beaucoup moins sujette aux problèmes.

En bref, goto peut être un bon choix dans certaines situations mais pas dans celle-ci. Ici, c'est juste la paresse à court terme.

22
Brian

Votre code est extrêmement non idiomatique et vous ne devriez jamais l'écrire. En gros, vous émulez C en C++. Mais d'autres ont fait des remarques à ce sujet et ont indiqué la RAII comme alternative.

Cependant, votre code ne fonctionnera pas comme vous le souhaitez, car ceci:

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

ever ne sera pas évalué à true (sauf si vous avez surchargé operator new de façon étrange). Si operator new est incapable d'allouer suffisamment de mémoire, il lève une exception. Never, ever renvoie 0, du moins pas avec cet ensemble de paramètres. il y a une surcharge de placement-new spéciale qui prend une instance de type std::nothrow et qui renvoie effectivement 0 au lieu de lancer une exception. Mais cette version est rarement utilisée dans le code normal. Certains codes de bas niveau ou certaines applications de périphériques intégrés pourraient en tirer parti dans des contextes où la gestion des exceptions est trop coûteuse.

Quelque chose de similaire est vrai pour votre bloc delete, comme Harald l'a dit: if (p) n'est pas nécessaire devant delete p.

De plus, je ne suis pas sûr que votre exemple ait été choisi intentionnellement, car ce code peut être réécrit comme suit:

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

Probablement pas une bonne idée .

16
Marc Charbonneau

En général, et à la surface, votre approche n’est pas fâcheuse, à condition que vous n’ayez qu’une seule étiquette et que les choses continuent à avancer. Par exemple, ce code:

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

Et ce code:

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

vraiment ne sont pas si différents les uns des autres. Si vous pouvez en accepter un, vous devriez pouvoir accepter l'autre.

Cependant, dans de nombreux cas, le modèle RAII (l'acquisition des ressources est une initialisation) peut rendre le code plus propre et plus facile à gérer. Par exemple, ce code:

int foo()
{
    Auto<int> pWhatEver = ...;

    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

est plus court, plus facile à lire et plus facile à gérer que les deux exemples précédents.

Donc, je recommanderais d’utiliser l’approche RAII si vous le pouvez.

11
Scott Wisniewski

Je pense que d’autres réponses (et leurs commentaires) ont couvert tous les points importants, mais voici une chose qui n’a pas encore été faite correctement:

À quoi votre code devrait-il ressembler à la place:

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;
  }
}

Eh bien, il est plus court et, autant que je sache, plus correct (gère correctement le cas du MOO) et, plus important encore, je n'ai pas besoin d'écrire de code de nettoyage ni de faire quoi que ce soit de spécial pour "s'assurer que ma valeur de retour est initialisée ".

Un problème avec votre code que j'ai seulement vraiment remarqué en écrivant ceci est: "Qu'est-ce que l'enfer est la valeur de bRetVal à ce stade?". Je ne sais pas car, il a été déclaré waaaaay ci-dessus, et il a été assigné pour la dernière fois à quand? Au-dessus de ça. Je dois lire l'intégralité de la fonction pour m'assurer de bien comprendre ce qui va être restitué.

Et comment puis-je me convaincre que la mémoire est libérée?

Comment est-ce que je sais que nous n'oublions jamais de passer directement à l'étiquette de nettoyage? Je dois travailler en arrière à partir de l'étiquette de nettoyage, trouver tous goto qui le pointe, et plus important encore, trouver ceux qui ne sont pas là. J'ai besoin de suivre tous les chemins de la fonction pour m'assurer que la fonction est nettoyée correctement. Pour moi, cela ressemble à du code spaghetti.

Code très fragile, car toutes les fois qu'une ressource doit être nettoyée, vous devez rappeler pour dupliquer votre code de nettoyage. Pourquoi ne pas l'écrire une fois, dans le type qui doit être nettoyé? Et puis vous fiez à ce qu’il soit exécuté automatiquement, chaque fois que nous en avons besoin?

8
jalf

Votre exemple n'est pas une exception.

Si vous utilisez goto pour nettoyer le code, alors, si une exception se produit avant le code de nettoyage, elle est complètement manquée. Si vous affirmez que vous n'utilisez pas d'exceptions, vous vous trompez, car new lancera bad_alloc s'il ne dispose pas de suffisamment de mémoire.

Également à ce stade (lorsque bad_alloc sera lancé), votre pile sera déroulée, manquant de tout le code de nettoyage de chaque fonction en remontant la pile d'appels afin de ne pas nettoyer votre code.

Vous devez chercher à faire des recherches sur les pointeurs intelligents. Dans la situation ci-dessus, vous pouvez simplement utiliser un std::auto_ptr<>.

Notez également qu'en code C++, il n'est pas nécessaire de vérifier si un pointeur est NULL (généralement parce que vous n'avez jamais de pointeurs RAW), mais parce que new ne renverra pas NULL (il renvoie).

Également en C++, contrairement à (C), il est courant de voir les premiers retours dans le code. En effet, RAII effectuera le nettoyage automatiquement, alors que dans le code C, vous devez vous assurer d’ajouter du code de nettoyage spécial à la fin de la fonction (un peu comme votre code).

8
Martin York

Vous devriez lire ce résumé de fil à partir des listes de diffusion du noyau Linux (en accordant une attention particulière aux réponses de Linus Torvalds) avant de créer une stratégie pour goto:

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

6
too much php

Au cours des huit années de programmation, j'ai beaucoup utilisé goto, principalement la première année où j'ai utilisé une version de GW-BASIC et un livre de 1980 qui n'a pas abouti. clear goto ne doit être utilisé que dans certains cas. La seule fois où j'ai utilisé goto en C++, c’est quand j’avais un code comme celui-ci, et je ne suis pas sûr qu’il existe un meilleur moyen.

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 seule situation que je connaisse dans laquelle goto est encore très utilisée est le langage d'assemblage mainframe, et les programmeurs que je connais s'assurent de documenter où le code saute et pourquoi.

6
Jared

Comme utilisé dans le noyau Linux, les goto utilisés pour le nettoyage fonctionnent bien lorsqu'une seule fonction doit exécuter 2 étapes ou plus qui peuvent nécessiter d'être annulées. Les étapes ne doivent pas nécessairement être une allocation de mémoire. Il peut s'agir d'un changement de configuration dans un morceau de code ou dans le registre d'un jeu de puces d'E/S. Les clichés ne devraient être utilisés que dans un petit nombre de cas, mais souvent, lorsqu'ils sont utilisés correctement, ils peuvent constituer la solution la meilleure. Ils ne sont pas mauvais. Ils sont un outil.

Au lieu 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;

vous pouvez faire la même chose avec les déclarations goto comme ceci:

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;

Il devrait être clair qu’étant donné ces deux exemples, l’un est préférable à l’autre. Quant à la foule RAII ... Il n’ya rien de mal à cette approche tant qu’ils peuvent garantir que le déroulement se déroulera toujours dans l’ordre inverse: 3, 2, 1. Enfin, certaines personnes n’utilisent pas les exceptions dans leur code et demandez aux compilateurs de les désactiver. Ainsi, tout le code ne doit pas être protégé contre les exceptions.

6
Harvey

En général, vous devriez concevoir vos programmes de manière à limiter le besoin de gotos. Utilisez les techniques OO pour "nettoyer" vos valeurs de retour. Il y a des façons de le faire qui n'exigent pas l'utilisation de gotos ou compliquent le code. Il existe des cas où les gotos sont très utiles (par exemple, des portées profondément imbriquées), mais doivent si possible être évités.

5
Marcin

Les inconvénients de GOTO sont assez bien discutés. J'ajouterais simplement que 1) il faut parfois les utiliser et savoir comment minimiser les problèmes, et 2) certaines techniques de programmation acceptées sont déguisées, alors soyez prudents.

1) Lorsque vous devez utiliser GOTO, comme dans les fichiers ASM ou .bat, pensez comme un compilateur. Si vous voulez coder

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

faire ce que fait un compilateur. Générez une étiquette dont le but est de sauter le corps, et non de faire ce qui suit. c'est à dire.

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

Ne pas

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

the_label_named_for_whatever_gets_done_next:

En d’autres termes, l’étiquette n’a pas pour objet de faire quelque chose, mais de passer quelque chose.

2) Ce que j'appelle GOTO déguisé, c'est tout ce qui pourrait être transformé en code GOTO + LABELS en définissant simplement quelques macros. Un exemple est la technique d'implémentation d'automates à états finis avec une variable d'état et une instruction while-switch.

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

peut se transformer en:

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

simplement en définissant quelques macros. À peu près n'importe quel FSA peut être transformé en code structuré sans moins. Je préfère rester à l'écart du code GOTO-in-déguisé, car il peut entrer dans les mêmes problèmes de code spaghetti que les gotos non dissimulés.

Ajoutée: Juste pour rassurer: je pense qu'une marque d'un bon programmeur reconnaît que les règles communes ne s'appliquent pas.

5
Mike Dunlavey

Goto fournit un meilleur ne vous répétez pas (DRY) lorsque la "logique d'extrémité" est commune à certains cas, mais pas tous. Surtout dans une déclaration "switch", j'utilise souvent goto lorsque certaines branches de switch ont un point commun.

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

Vous avez probablement remarqué qu'introduire des accolades supplémentaires suffit à satisfaire le compilateur lorsque vous avez besoin d'une telle fusion d'extrémité au milieu d'une routine. En d'autres termes, vous n'avez pas besoin de tout déclarer en haut; c'est une lisibilité inférieure en effet.

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

Avec les dernières versions de Visual Studio détectant l'utilisation de variables non initialisées, je suis toujours en train d'initialiser la plupart des variables même si je pense qu'elles peuvent être affectées dans toutes les branches - il est facile de coder une instruction "traçage" qui référence une variable cela n'a jamais été attribué car votre esprit ne considère pas l'instruction de traçage comme un "code réel", mais bien entendu, Visual Studio détectera toujours une erreur.

En outre, ne vous répétez pas, attribuer des noms d'étiquette à une telle logique d'extrémité semble même aider mon esprit à garder les choses claires en choisissant des noms d'étiquette agréables. Sans une étiquette significative, vos commentaires pourraient finir par dire la même chose.

Bien sûr, si vous allouez des ressources, alors si auto-ptr ne vous convient pas, vous devez utiliser un try-catch, mais la fin de la fusion-ne-répétez pas vous-même se produit assez souvent lorsque la sécurité des exceptions est pas une solution.

En résumé, bien que goto puisse être utilisé pour coder des structures similaires à des spaghettis, dans le cas d’une séquence de fin de queue commune à certains cas, le goto améliore la lisibilité du code et même sa maintenabilité. si vous voulez copier/coller des choses, alors bien plus tard, quelqu'un pourra mettre à jour l'un et l'autre. C'est donc un autre cas où être fanatique d'un dogme peut être contre-productif.

5
pngaz

Utiliser goto pour aller dans une section de nettoyage va causer beaucoup de problèmes.

Premièrement, les sections de nettoyage sont sujettes à des problèmes. Ils ont une faible cohésion (aucun rôle réel ne peut être décrit en termes de ce que le programme tente de faire), un couplage élevé (la correction dépend énormément d'autres sections de code) et ne sont pas du tout protégés contre les exceptions. Voyez si vous pouvez utiliser des destructeurs pour le nettoyage. Par exemple, si int *p est remplacé par auto_ptr<int> p, les points p seront automatiquement publiés.

Deuxièmement, comme vous l'avez fait remarquer, cela va vous obliger à déclarer les variables bien avant leur utilisation, ce qui compliquera la compréhension du code.

Troisièmement, même si vous proposez une utilisation assez disciplinée de la méthode goto, vous serez tenté de les utiliser de manière plus souple et le code deviendra difficile à comprendre.

Il y a très peu de situations où un goto est approprié. La plupart du temps, lorsque vous êtes tenté de les utiliser, cela signifie que vous agissez mal.

4
David Thornley

S'agissant d'un sujet classique, je répondrai par la déclaration Go-to de Dijkstra considérée comme nuisible (publiée à l'origine dans ACM).

4
mstrobl

Le but de l'idiome C en C, qui consistait en une fonction unique, consistait à regrouper toutes les tâches de nettoyage à un seul endroit. Si vous utilisez des destructeurs C++ pour gérer le nettoyage, ce n'est plus nécessaire - le nettoyage sera effectué quel que soit le nombre de points de sortie d'une fonction. Donc, dans du code C++ correctement conçu, ce genre de chose n’est plus nécessaire.

3
Head Geek

Les deux seules raisons pour lesquelles j'utilise goto dans mon code C++ sont les suivantes:

  • Rompre une boucle imbriquée de niveau 2 ou supérieur
  • Flux compliqués comme celui-ci (un commentaire dans mon programme):

    /* 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.
    
    */
    

Pour la lisibilité du code, après ce commentaire, j'ai défini l'étiquette step1 et je l'ai utilisée aux étapes 2 et 3. En fait, dans plus de 60 fichiers sources, seule cette situation et un niveau imbriqué sur 4 sont les endroits où j'ai utilisé goto. Seulement deux endroits.

Beaucoup de gens paniquent avec les gotos sont diaboliques; ils ne sont pas. Cela dit, vous n’en aurez jamais besoin; il y a presque toujours un meilleur moyen.

Lorsque je me trouve «obligé» de faire quelque chose de ce genre, je trouve presque toujours que mon code est trop complexe et peut être facilement décomposé en quelques appels de méthode plus faciles à lire et à traiter. Votre code d'appel peut faire quelque chose comme:

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

Ce n’est pas parfait, mais c’est beaucoup plus facile à suivre car toutes vos méthodes seront nommées pour indiquer clairement le problème.

La lecture des commentaires, cependant, devrait indiquer que votre équipe a des problèmes plus urgents que la manipulation.

3
Bill K

Le code que vous nous donnez est du code C (presque) écrit dans un fichier C++ . Le type de nettoyage de la mémoire que vous utilisez serait correct dans un programme C n’utilisant pas de code/bibliothèques C++.

En C++, votre code est simplement dangereux et peu fiable. En C++, le type de gestion que vous demandez est fait différemment. Utilisez des constructeurs/destructeurs. Utilisez des pointeurs intelligents. Utilisez la pile. En un mot, utilisez RAII .

Votre code pourrait (c'est-à-dire, en C++, DEVRAIT être écrit) comme suit:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<int> p = new int;

   // Lot of code...

   return bRetVal ;
}

(Notez que créer un int est quelque peu idiot dans le code réel, mais vous pouvez le remplacer par n'importe quel type d'objet, et alors, cela a plus de sens). Imaginons que nous ayons un objet de type T (T pourrait être un int, une classe C++, etc.). Alors le code devient:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<T> p = new T;

   // Lot of code...

   return bRetVal ;
}

Ou encore mieux, en utilisant la pile:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   T p ;

   // Lot of code...

   return bRetVal;
}

Quoi qu’il en soit, l’un des exemples ci-dessus est une grandeur plus facile à lire et à sécuriser que votre exemple.

RAII a de nombreuses facettes (utilisation de pointeurs intelligents, pile, utilisation de vecteurs au lieu de tableaux de longueur variable, etc.), mais dans l’ensemble, il s’agit d’écrire le moins de code possible, laissant le compilateur nettoyer le contenu au bon moment.

2
paercebal

Je ne vais pas dire que goto est toujours mauvais, mais vous en utilisez très certainement. Ce genre de "sections de nettoyage" était assez courant au début des années 1990, mais son utilisation pour le nouveau code est un pur mal.

1
Nemanja Trifunovic

Ce code a un tas de problèmes, dont la plupart ont déjà été signalés, par exemple:

  • La fonction est trop longue. refactoriser du code dans des fonctions séparées peut aider.

  • Utiliser des pointeurs lorsque des instances normales fonctionneront probablement très bien.

  • Ne profite pas de STL types tels que auto_ptr

  • Vérifier de manière incorrecte les erreurs et ne pas capturer les exceptions. (Je dirais que la recherche de MOO est inutile sur la grande majorité des plates-formes, car si vous manquez de mémoire, vous rencontrez des problèmes plus importants que votre logiciel ne peut résoudre, à moins d'écrire le système d'exploitation lui-même.)

Je n'ai jamais eu besoin d'un goto et j'ai toujours constaté que l'utilisation de goto était le symptôme d'un ensemble de problèmes plus importants. Votre cas ne semble pas faire exception.

1
metao

Le moyen le plus simple d'éviter ce que vous faites ici est de placer tout ce nettoyage dans une sorte de structure simple et de créer une instance de celle-ci. Par exemple au lieu de:

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();
}

vous devriez plutôt faire ce nettoyage en quelque sorte comme:

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
   {
      ...
   }
}
1
Michel

Je pense que l’utilisation de la commande goto pour le code de sortie est mauvaise, car il existe de nombreuses autres solutions peu coûteuses telles que la création d’une fonction de sortie et le renvoi de la valeur de la fonction de sortie, le cas échéant. Cela ne devrait normalement pas être nécessaire dans les fonctions membres, sans quoi cela pourrait indiquer un peu trop de code.

En règle générale, la seule exception à la règle "no goto" lors de la programmation concerne la sortie de boucles imbriquées à un niveau spécifique, ce que je n'ai rencontré que la nécessité de faire lorsque je travaille sur la programmation mathématique.

Par exemple:

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:;
}
1
Hazok

Tout ce qui précède est valable, vous pouvez également vérifier si vous êtes en mesure de réduire la complexité de votre code et de réduire le besoin de lecture en réduisant la quantité de code figurant dans la section "Lot de code" dans votre exemple. Additionaly delete 0 est une déclaration C++ valide.

1
Harald Scheirich

Utiliser des étiquettes GOTO en C++ est une mauvaise façon de programmer. Vous pouvez réduire le besoin en programmant OO] (déconstructeurs!) Et en essayant de garder les procédures aussi petites possible. .

Votre exemple a l'air un peu bizarre, il y a inutile de supprimer un pointeur NULL. Et de nos jours, une exception est levée lorsqu'un pointeur ne peut pas être alloué.

Votre procédure pourrait simplement être écrite comme suit:

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;
}

Vous devez placer un bloc try dans le programme principal gérant les problèmes de mémoire, qui informe l'utilisateur du manque de mémoire, ce qui est très rare} ... ... (le système d'exploitation lui-même n'en informe-t-il pas également ?)

Notez également qu'il n'est pas toujours nécessaire d'utiliser un pointeur, ils ne sont utiles que pour les choses dynamiques. (Créer une chose dans une méthode qui ne dépend pas de l'entrée de n'importe où n'est pas vraiment dynamique)

1
Tom Wijsman

Il y a quelques années, j'ai créé un pseudo-idiome qui évite le goto et ressemble vaguement à la gestion des exceptions en C. Il a probablement déjà été inventé par quelqu'un d'autre, alors je suppose que je l'ai "découvert de manière indépendante" :)

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;
}
1
ggambett

J'ai peut-être manqué quelque chose: vous sautez à l'étiquette Exit si P est null, puis testez pour voir si ce n'est pas null (ce qui n'est pas le cas) pour voir si vous devez le supprimer (ce qui n'est pas nécessaire car il n'a jamais été alloué la première place).

Le if/goto ne veut pas, et n'a pas besoin de supprimer p. Remplacer le goto par un retour false aurait le même effet (et vous pourriez alors supprimer l'étiquette de sortie). 

Les seuls endroits où je connaisse l'utilité de goto sont profondément enfouis dans de mauvais analyseurs syntaxiques (ou analyseurs lexicaux) et dans la simulation de machines à états (enfouis dans une masse de macros CPP). Dans ces deux cas, ils ont été utilisés pour simplifier la logique très tordue, mais c'est très rare.

Les fonctions (appels A '), Try/Attrapes et setjmp/longjmps sont toutes des méthodes plus efficaces pour éviter un problème de syntaxe difficile.

Paul.

0
Paul W Homer

Ignorant le fait que new ne retournera jamais NULL, prenez votre code:

  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;
  }

et écrivez-le comme ceci:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p = new int;

     if (p!=NULL)
     {
        // Lot of code...

        delete p;
     }
     else
     {
        cout<<" OOM \n";
     }

     return bRetVal;
  }
0
jussij

Essayez de cette façon:

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
   }
   else
   {
       // Lot of code...
   }

   if (p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

Dans la section "Lot de code", "Lot de code" indique que vous devriez probablement refactoriser cette section en une ou plusieurs méthodes ou fonctions.

0
Matthew

Utiliser "GOTO" changera les "logiques" d'un programme et la manière dont vous vous interprétez ou imaginez que cela fonctionnerait.

Éviter les commandes GOTO a toujours fonctionné pour moi, alors devinez quand vous pensez en avoir besoin, tout ce dont vous avez besoin est peut-être une nouvelle conception.

Cependant, si nous examinons cela au niveau général, le jusing "saute" revient à utiliser GOTO et qui est utilisé tout le temps, MAIS dans Assembly, vous pouvez effacer ce que vous savez que vous avez sur la pile et d’autres registres transmettre.

Ainsi, lors de l'utilisation de GOTO, je m'assurerais que le logiciel "apparaît" comme le feraient les co-codeurs, GOTO aura un "mauvais" effet sur votre logiciel imho.

C’est donc plus une explication à pourquoi ne pas utiliser GOTO et non une solution de remplacement, car c’est TRÈS BEAUCOUP, comme tout le reste est construit.

0
Filip Ekberg

Les commentaires précédents sont tous de bonnes raisons de ne pas utiliser goto.

Je peux parler d'expérience que pour d'autres programmeurs qui pourraient avoir besoin de maintenir votre code, il est très difficile de suivre la logique. Je suis tombé dans une situation de code de spaghetti lourd et, avec mon arrière-plan OO, c’était un cauchemar de déboguer et d’apporter des modifications. Oui, ce code utilisait également les fonctions de nettoyage pour les fonctions de nettoyage. Très frustrant quand pas nécessaire. N'utilisez pas de goto sauf en cas d'absolue nécessité.

0
Jeff Schmidt

Alien01 a écrit: Je travaille actuellement sur un projet dans lequel les instructions goto sont fortement utilisées. L'objectif principal des instructions goto est d'avoir une section de nettoyage dans la routine plutôt que plusieurs instructions de retour.

En d'autres termes, vous voulez séparer la logique du programme des simples routines fastidieuses répétitives, comme libérer une ressource qui pourrait être réservée dans différents emplacements de code.

La technique de traitement des exceptions est une logique de traitement des erreurs qui fonctionne en parallèle avec la logique de programme. C'est une solution plus élégante puisqu'elle offre une telle séparation tout en offrant la possibilité de déplacer le contrôle vers d'autres blocs de code exactement comme le fait l'instruction goto. J'ai donc modifié votre script pour qu'il ressemble à ceci:

class auxNullPtrException : public std::exception {
    public:
        auxNullPtrException::auxNullPtrException()
            : std::exception( " OOM \n") {}
    };

    BOOL foo()
    {
        BOOL bRetVal = FALSE;
        try {
            int *p = NULL;
            p = new int;
            if (p == NULL)
            {
                throw auxNullPtrException();
            }
            // Lot of code...
         }
         catch(auxNullPtrException & _auxNullPtrException)
         {
             std::cerr<<_auxNullPtrException.what();
             if(p)
             {
                 delete p;
                 p = NULL;
             }
         }
         return bRetVal;
    }
0
Josef

De tous les commentaires précédents:

  1. goto est très très mauvais
  2. Cela rend le code difficile à lire et à comprendre.
  3. Cela peut causer le problème bien connu "code spaghetti"
  4. Tout le monde est d'accord pour dire que cela ne devrait pas être fait.

Mais j'utilise dans le scénario suivant

  1. Il a l'habitude d'aller de l'avant et à un seul label.
  2. la section goto est utilisée pour nettoyer le code et définir une valeur de retour . Si je n'utilise pas goto, je dois créer une classe de chaque type de données. Comme si j'avais besoin d'intégrer * dans une classe.
  3. Il est suivi dans tout le projet.

Je conviens que c'est mauvais, mais cela rend les choses beaucoup plus faciles si on les suit correctement.

0
anand