33 votes

Comment éviter l'utilisation de goto et rompre efficacement les boucles imbriquées

Je dirais que c'est un fait que l'utilisation de goto est considéré comme une mauvaise pratique quand il s'agit de la programmation en C/C++.

Cependant, vu le code suivant

for (i = 0; i < N; ++i) 
{
    for (j = 0; j < N; j++) 
    {
        for (k = 0; k < N; ++k) 
        {
            ...
            if (condition)
                goto out;
            ...
        }
    }
}
out:
    ...

Je me demande comment faire pour obtenir le même comportement efficace à l'aide de goto. Ce que je veux dire, c'est que nous pouvions faire quelque chose comme la vérification de condition à la fin de chaque boucle, par exemple, mais autant que je sache goto va générer simplement un ensemble d'instructions qui sera un jmp. C'est donc le moyen le plus efficace de le faire je pense.

Est-il un autre qui est considéré comme une bonne pratique? Suis-je tort quand je dis que c'est considéré comme une mauvaise pratique à utiliser goto? Si je suis, serait-ce l'un de ces cas où il est bon de l'utiliser?

Merci

50voto

Max Langhof Points 19174

La (imo) meilleure version non goto ressemblerait à ceci:

 void calculateStuff()
{
    // Please use better names than this.
    doSomeStuff();
    doLoopyStuff();
    doMoreStuff();
}

void doLoopyStuff()
{
    for (i = 0; i < N; ++i) 
    {
        for (j = 0; j < N; j++) 
        {
            for (k = 0; k < N; ++k) 
            {
                /* do something */

                if (/*condition*/)
                    return; // Intuitive control flow without goto

                /* do something */
            }
        }
    }
}
 

Le fractionnement est également probablement une bonne idée car il vous aide à garder vos fonctions courtes, votre code lisible (si vous nommez les fonctions mieux que moi) et les dépendances faibles.

26voto

Steve Summit Points 16971

Si vous avez profondément boucles imbriquées comme ça et vous devez sortir, je crois qu' goto est la meilleure solution. Certains langages (C) ont un break(N) déclaration qui va sortir de plus d'une boucle. La raison C ne l'a pas, c'est que c'est même pire qu'un goto: vous avez à compter le nombre de boucles imbriquées à comprendre ce qu'il fait, et il est vulnérable à quelqu'un de venir plus tard, et l'ajout ou la suppression d'un niveau d'imbrication, sans remarquer que la rupture de comptage doit être ajusté.

Oui, gotos sont généralement mal vu. À l'aide d'un goto ici n'est pas une bonne solution; c'est simplement le moins de plusieurs maux.

Dans la plupart des cas, la raison pour laquelle vous devez sortir de profondément boucle imbriquée est parce que vous êtes à la recherche de quelque chose, et vous l'avez trouvé. Dans ce cas (et comme plusieurs autres commentaires et des réponses ont suggéré), je préfère déplacer la boucle imbriquée pour sa propre fonction. Dans ce cas, un return hors de la boucle interne accomplissant votre tâche très proprement.

(Il y a ceux qui disent que les fonctions doivent toujours revenir à la fin, pas du milieu. Ces gens vont dire que la simple pause-it-out-pour-une-fonction de la solution est donc invalide, et qu'ils avaient la force de l'utilisation de la même maladroit break-out-of-the-intérieur de la boucle de la technique(s), même si la recherche avait été séparée de sa propre fonction. Personnellement, je pense que ces gens sont dans l'erreur, mais votre kilométrage peut varier.)

Si vous insistez sur la non-utilisation d'un goto, et si vous insistez sur la non utilisation d'une fonction séparée avec un début de retour, alors oui, vous pouvez faire quelque chose comme le maintien supplémentaire Boolean variables de contrôle et de les tester de manière redondante dans la condition de contrôle de chaque boucle imbriquée, mais c'est juste une nuisance et un gâchis. (C'est l'un des plus grands maux que je disais à l'aide d'un simple goto est inférieur.)

17voto

ricco19 Points 513

Je pense qu' goto est un perfectely sane chose à faire ici, et est l'un de ses exceptionnels cas d'utilisation par le C++ lignes Directrices de Base.

Cependant, peut-être une autre solution à envisager est une IIFE lambda. À mon avis, c'est un peu plus élégant que de déclarer une fonction distincte!

[&] {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; ++k)
                if (condition)
                    return;
}();

Grâce à JohnMcPineapple sur reddit pour cette suggestion!

12voto

dbush Points 8590

Dans ce cas, vous n'avez pas wan pas pour éviter d'utiliser goto.

En général, l'utilisation de l' goto devrait être évitée, il existe cependant des exceptions à cette règle, et votre cas est un bon exemple de l'un d'eux.

Nous allons étudier les solutions de rechange:

for (i = 0; i < N; ++i) {
    for (j = 0; j < N; j++) {
        for (k = 0; k < N; ++k) {
            ...
            if (condition)
                break;
            ...
        }
        if (condition)
            break;
    }
    if (condition)
        break;
}

Ou:

int flag = 0
for (i = 0; (i < N) && !flag; ++i) {
    for (j = 0; (j < N) && !flag; j++) {
        for (k = 0; (k < N) && !flag; ++k) {
            ...
            if (condition) {
                flag = 1
                break;
            ...
        }
    }
}

Aucune de ces aussi concis ou aussi lisible que l' goto version.

À l'aide d'un goto est considéré comme acceptable dans le cas où vous êtes seulement à sauter en avant (pas en arrière) et cela rend votre code plus lisible et compréhensible.

En revanche, si vous utilisez goto de sauter dans les deux directions, ou de sauter dans un champ d'application qui pourrait contourner l'initialisation d'une variable, qui serait mauvais.

Voici un mauvais exemple de goto:

int x;
scanf("%d", &x);
if (x==4) goto bad_jump;

{
    int y=9;

// jumping here skips the initialization of y
bad_jump:

    printf("y=%d\n", y);
}

Un compilateur C++ lèvera une erreur ici parce que l' goto saute par-dessus l'initialisation de y. Les compilateurs C cependant la compilation, et le code ci-dessus va invoquer un comportement indéfini lorsque vous tentez d'imprimer y qui sera initialisée si l' goto se produit.

Un autre exemple de l'utilisation appropriée de l' goto est dans la gestion des erreurs:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        goto end1;
    }
    char *p2 = malloc(10);
    if (!p2) {
        goto end2;
    }
    char *p3 = malloc(10);
    if (!p3) {
        goto end3;
    }
    // do something with p1, p2, and p3

end3:
    free(p3);
end2:
    free(p2);
end1:
    free(p1);
}

Elle effectue tous les de la de nettoyage à la fin de la fonction. Comparez cela à l'alternative:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        return;
    }
    char *p2 = malloc(10);
    if (!p2) {
        free(p1);
        return;
    }
    char *p3 = malloc(10);
    if (!p3) {
        free(p2);
        free(p1);
        return;
    }
    // do something with p1, p2, and p3

    free(p3);
    free(p2);
    free(p1);
}

Lorsque le nettoyage est effectué en plusieurs endroits. Si vous ajoutez plus de ressources que nécessaire de les nettoyer, vous devez n'oubliez pas d'ajouter le nettoyage dans tous ces endroits, plus le nettoyage de toutes les ressources qui ont été obtenus précédemment.

L'exemple ci-dessus est plus pertinent pour C qu'en C++, car dans le dernier cas, vous pouvez utiliser les classes avec une bonne destructeurs et des pointeurs intelligents pour éviter un nettoyage manuel.

4voto

JeJo Points 12135

La variante - 1

Vous pouvez faire quelque chose comme suit:

  1. Définir un bool variable dans le début isOkay = true
  2. Tous vos forboucle conditions, d'ajouter une condition supplémentaire isOkay == true
  3. Lorsque votre personnalisé de votre condition est satisfaite/ échoue, définissez isOkay = false.

Cela rendra vos boucles d'arrêt. Un extra - bool variable serait parfois pratique si.

bool isOkay = true;
for (int i = 0; isOkay && i < N; ++i)
{
    for (int j = 0; isOkay && j < N; j++)
    {
        for (int k = 0; isOkay && k < N; ++k)
        {
            // some code
            if (/*your condition*/)
                 isOkay = false;
        }
     }
}

Alternative - 2

Deuxièmement. si les itérations de boucle sont en fonction, le meilleur choix est de return résultat, quand jamais la coutume condition est satisfaite.

bool loop_fun(/* pass the array and other arguments */)
{
    for (int i = 0; i < N ; ++i)
    {
        for (int j = 0; j < N ; j++)
        {
            for (int k = 0; k < N ; ++k)
            {
                // some code
                if (/* your condition*/)
                    return false;
            }
        }
    }
    return true;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X