6
votes

Utilisation de goto pour une sortie proprement une boucle

J'ai une question sur l'utilisation de l'instruction GOTO en C ++. Je crois comprendre que ce sujet est controversé et je ne suis pas intéressé par des conseils ou des arguments radicaux (je m'empêche généralement d'utiliser goto code>). J'ai plutôt une situation spécifique et je veux comprendre si ma solution, qui utilise la déclaration GOTO, est une bonne ou non. Je ne m'appellerais pas nouveau à C ++, mais ne me classerais pas non plus comme un programmeur de niveau professionnel. La partie du code qui a généré ma question tourne dans une boucle infinie une fois commencé. Le débit général du fil dans le pseudocode est le suivant:

void ControlLoop::main_loop()
{
    InitializeAndCheckHardware(pHardware) //pHardware is a pointer given from outside
    //The main loop
    while (m_bIsRunning)
    {
        simulated_time += time_increment; //this will probably be += 0.001 seconds
        ReadSensorData();
        if (data_is_bad) {
            m_bIsRunning = false;
            goto loop_end;
        }    
        ApplyFilterToData();
        ComputeControllerOutput();
        SendOutputToHardware();
        ProcessPendingEvents();

        while ( GetWallClockTime() < simulated_time ) {}
        if ( end_condition_is_satisified ) m_bIsRunning = false;
    }
    loop_end:
    DeInitializeHardware(pHardware);
}


9 commentaires

"Cela n'a pas beaucoup de sens pour moi d'utiliser Raii" il est toujours logique d'utiliser Raii. Toujours. Tout le temps.


Pourquoi ne pas utiliser pause ici?


S'il n'y a qu'une seule boucle que vous rompez, utilisez break ; Il est plus propre et plus clair. Si vous avez plusieurs niveaux de boucle, ou si vous êtes à l'intérieur d'un commutateur , et que vous devez quitter toutes les boucles, le goto est ok.


Cela peut être une question plus appropriée pour programmeurs.stackexchange.com


Je pense qu'il a fait le point pourquoi il n'utilise pas de pause, et nous devrions discuter davantage au lieu de simplement demander pourquoi ne pas utiliser la pause


@Jamesmcnellis fait l'idée d'une sorte d'objet "session" sembler comme la voie à suivre ici?


Basé sur ce que vous avez montré ici, le conteneur Raii pourrait être aussi simple que struct matérielsInitializer {explicite "HardwareInitializer (matériel * HW): _hw (hw) {initializeandcheckableware (_hw); } ~ HardwareInitializer () {DEINITIALIZEHARDWARDWARD (_HW); } Matériel * _hw; } . Il suffit de faire le nettoyage automatique.


Le matériel supérieur ci-dessus exemple de classe doit être non gêné. Ces boîtes de commentaire sont trop petites pour le code d'écriture. Il est trop tard pour le changer, malheureusement.


@Jamesmcnellis c'est bon. Je comprends ce que vous voulez dire et que vous avez également mentionné dans la réponse d'ED S. ci-dessous. Est-ce juste pour empêcher l'erreur de l'utilisateur ou pourrait le laisser copier causer des bugs plus subtils?


7 Réponses :


5
votes

Éviter l'utilisation de goto est une chose assez solide à faire dans le développement orienté objet en général.

Dans votre cas, pourquoi pas simplement utiliser Break pour quitter La boucle? xxx

Tant que votre question: Votre utilisation de goto me ferait mal à l'aise. La seule raison pour laquelle break est moins lisible est votre admission à ne pas être un développeur C ++ fort. À tout développeur assaisonné d'une langue similaire, pause va à la fois mieux, ainsi que de fournir une solution plus propre que goto .

en particulier Je ne suis tout simplement pas d'accord que xxx

est plus lisible que xxx

qui dit littéralement la même chose avec en syntaxe.


5 commentaires

Ma principale préoccupation était que la principale boucle du code réel est assez longue, et je pense donc que l'utilisation de la pause en fait un peu moins claire lorsque le flux de programme ira à la lecture immédiate. Merci, cependant, pour votre commentaire. Cela semble être un point de vue raisonnable.


Tant que vous n'avez pas d'espacement étrange dans votre code, votre code doit alors s'écouler de manière assez lanière à l'aide du pendant {} avec tout développeur expérimenté. Il est particulièrement facile de suivre moins de la boucle.


En outre, il est possible qu'un IDE vous indique exactement où le ira (bien que je ne sache pas s'il existe des idées qui ont cette fonctionnalité particulière).


@Hunter: Mon principale préoccupation était que la boucle principale dans le code réel est assez longue => Lorsqu'une fonction est trop longue pour s'adapter confortablement sur votre écran, il est temps de commencer à partir de morceaux de factorisation en plus petites fonctions; Que vous utilisiez goto ou break à ce stade est hors de propos.


@Matthieu merci. Je suis d'accord avec cette notion. Dans des cas isolés, cependant, je trouve cela assez énervant de facturer des morceaux. Je ne dis pas que tu as tort, ça peut être ennuyeux. Lorsque les différentes parties d'une séquence longue partagent beaucoup d'informations contextuelles, les fonctions doivent avoir de nombreux arguments ou devraient partager de nombreuses données en modifiant les membres de la classe, ce qui pourrait (quelque peu) cacher la relation de causalité entre les pièces. Ou, un type d'objet supplémentaire, principalement sans signification, est créé pour mélanger les données.



1
votes

au lieu d'utiliser goto , vous devez utiliser break; pour échapper aux boucles.


3 commentaires

Pour un seul niveau de boucle, c'est vrai. Qu'en est-il de plusieurs niveaux de boucle?


L'affiche utilisait GOTO pour sortir de cette seule boucle, donc la pause est la meilleure option (IMO) dans le cas de l'OP.


D'accord; Vous avez répondu à la lettre de la question. Mais les réponses qui vont au-delà de la lettre de la question et répondent à l'esprit de la question sont plus susceptibles d'être éliminées. Le problème n'est pas que goto est automatiquement mauvais; Ce n'est pas correct dans l'exemple, mais il n'est pas difficile de concevoir des circonstances dans lesquelles il est approprié.



3
votes

Mise à jour

Si votre principale préoccupation est la boucle tandis que la boucle est trop longue, vous devriez viser à le rendre plus court, C ++ est une langue oo et oo est pour des choses divisées aux petites pièces et Composant, même en général non-Oo, nous pensons généralement que nous devrions briser une méthode / boucle en petite et la rendre courte facile à lire. Si une boucle ait 300 lignes dedans, aucune rupture / goto ne sauvegardez pas vraiment votre séjour là-bas n'est pas?

Mise à jour

i Je ne suis pas contre goto mais je ne l'utiliserai pas ici comme vous le faites, je préfère simplement utiliser pause , généralement à un développeur qu'il a vu une pause / Strong> Là, il sais que cela signifie que cela signifie aller à la fin du moment, et avec ce m_bisrunning = false il peut facilement conscience de cela, il quitte la boucle en quelques secondes. Oui un goto peut enregistrer le temps pendant quelques secondes pour le comprendre, mais il peut également faire sentir les gens nerveux de votre code.

La chose que je peux imaginer que j'utilise un goto serait de sortir d'une boucle de deux niveaux: xxx


2 commentaires

Merci, Simon. La boucle principale n'est pas ridiculement longue (~ 150 lignes), mais est plus longue qu'un écran, du moins pour moi. Je pourrais certainement le casser plus. J'ai déjà fait cela dans le degré que j'ai pensé raisonnable, cependant. Cela peut appartenir à une autre question, et si vous n'hésitez pas à ne pas répondre, mais que vous feriez de telles méthodes de classe de sous-routines qui communiquent en stockant des données dans la classe ou fonctionnent simplement dans la même unité de traduction qui prennent des arguments pour la sortie.


Eh bien, cela dépend vraiment, et je ne sais pas ce que je ferais jusqu'à ce que je le fasse;) la règle que je suivrais surtout ferait que chaque classe se concentre sur une zone avec juste faire une chose, mais personne n'est parfait et je pourrais fait du codage laid que je ne peux pas au courant. Je dirais ici laissez le code tel qu'il est là si vous et votre équipe se sentez tous bien avec ce goto et personne ne veulent le supprimer.



3
votes

Avec votre une, une condition singulière qui provoque la pause de la boucle tôt, j'utiliserais simplement une pause code>. Pas besoin d'un goto code> c'est ce que pause code> est pour.

Cependant, si tout em> de ces appels de fonction peut lancer une exception ou si vous finir par avoir besoin de multiples pause code> S Je préférerais un conteneur de style Raii, il s'agit du type de chose exact destructeurs. Vous effectuez toujours l'appel à DEINITIALIZEHARDWARDAWARDWARDWARDAWARD CODE>, SO ... P>

// todo: add error checking if needed
class HardwareWrapper {
public:
    HardwareWrapper(Hardware *pH) 
      : _pHardware(pH) { 
        InitializeAndCheckHardware(_pHardware);
    }

    ~HardwareWrapper() {
        DeInitializeHardware(_pHardware);
    }

    const Hardware *getHardware() const {
        return _pHardware;
    }

    const Hardware *operator->() const {
        return _pHardware;
    }

    const Hardware& operator*() const {
        return *_pHardware;
    }

private:
    Hardware *_pHardware;
    // if you don't want to allow copies...
    HardwareWrapper(const HardwareWrapper &other);
    HardwareWrapper& operator=(const HardwareWrapper &other);
}

// ...

void ControlLoop::main_loop()
{
    HardwareWrapper hw(pHardware);
    // code
}


2 commentaires

Cette solution est très propre. Merci.


Étant donné qu'il existe une fonction initialisizxxx , je vous suggère de mettre l'initialisation dans le constructeur, de la symétrie.



0
votes

goto n'est pas une bonne pratique pour sortir de la boucle lorsque pause code> est une option.

Aussi, dans des routines complexes, il est bon de n'avoir qu'une seule logique de sortie (avec nettoyage) placée à la fin. Goto est parfois utilisé pour sauter à la logique de retour. P>

exemple du pilote de bloc VMDK QEMU VMDK: P>

static int vmdk_open(BlockDriverState *bs, int flags)
{
    int ret;
    BDRVVmdkState *s = bs->opaque;

    if (vmdk_open_sparse(bs, bs->file, flags) == 0) {
        s->desc_offset = 0x200;
    } else {
        ret = vmdk_open_desc_file(bs, flags, 0);
        if (ret) {
            goto fail;
        }
    }
    /* try to open parent images, if exist */
    ret = vmdk_parent_open(bs);
    if (ret) {
        goto fail;
    }
    s->parent_cid = vmdk_read_cid(bs, 1);
    qemu_co_mutex_init(&s->lock);

    /* Disable migration when VMDK images are used */
    error_set(&s->migration_blocker,
              QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
              "vmdk", bs->device_name, "live migration");
    migrate_add_blocker(s->migration_blocker);

    return 0;

fail:
    vmdk_free_extents(bs);
    return ret;
}


0 commentaires

1
votes

Il y a plusieurs alternatives à goto : pause , Continuer et retour en fonction de la situation.

Cependant, vous devez garder à l'esprit que les deux pause et continuer sont limités en ce sens qu'ils n'affectent que la boucle la plus interne. retour d'autre part n'est pas affecté par cette limitation.

en général, si vous utilisez un goto à sortie Une portée particulière, vous pouvez alors refacteur en utilisant une autre fonction et une instruction renvoyer à la place. Il est probable que cela facilitera le code de lire en tant que bonus: xxx

Remarque: Si votre corps de fonction ne peut pas s'adapter confortablement sur votre écran, vous faites Cela ne va pas.


1 commentaires

Merci, c'est une autre solution valant la peine d'être envisagée.



0
votes

Je vois des charges de personnes suggérant pause au lieu de goto . Mais casse n'est pas "meilleur" (ou "pire") que goto .

L'Inquisition contre goto Commencé efficacement avec Dijkstra's "Aller à considéré comme nocif" de papier "de" dommages nuisible en 1968, lorsque le code spaghetti était la règle et les choses telles que la structure du bloc-structuré si et tandis que les déclarations étaient toujours considérées comme considérées Bord de coupe . Algol 60 les avait, mais c'était essentiellement une langue de recherche utilisée par les universitaires (cf. ml aujourd'hui); Fortran, une des langues dominantes à l'époque, ne les obtiendrait pas pendant 9 ans!

Les points principaux du papier de Dijkstra sont les suivants:

  1. Les humains sont bons au raisonnement spatial et les programmes de blocs structurés capitalisent sur cela, car les actions de programme qui se produisent les uns des autres à temps sont décrites à proximité de "espace" (code de programme);
  2. Si vous évitez goto de toutes ses formes différentes, il est possible de connaître les choses sur les états possibles des variables à chaque position lexicale du programme. En particulier, à la fin d'un pendant la boucle , vous savez que la condition de cette boucle doit être fausse. Ceci est utile pour le débogage. (Dijkstra ne dit pas tout à fait cela, mais vous pouvez en déduire.)

    pause , comme goto (et précoce retour s et exceptions ...), réduit (1) et élimine (2 ). Bien sûr, en utilisant break vous permet souvent d'éviter d'écrire une logique compliquée pour le pendant la condition , vous mettant un gain net dans la compréhensibilité - et exactement la même chose s'applique à goto .


1 commentaires

Merci pour l'explication. Il est également bon de comprendre que sans une pause ou goto dans mon cas particulier, le reste de la boucle tandis que la boucle doit être protégé par un supplémentaire si , car l'envoi de sortie sur le matériel basé sur une mauvaise entrée pourrait être désastreux, c'est-à-dire que la réponse correcte aux données incorrectes est "Arrêt maintenant".