Ist goto xy wirklich so böse?



  • Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃



  • zufallswert schrieb:

    Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃

    Dazu gibt's jetzt nicht viel zu sagen. Wenn du es nicht für nötig befindest irgendwas sachliches zu antworten, dann sage ich einfach mal: nein, da liegst du völlig falsch. Sorry.


  • Mod

    einwegflasche schrieb:

    Daran hatte ich noch gar nicht gedacht. Ich hatte mich aber immer darüber geärgert, dass ich dafür immer Zustandsflags mit schleppen musste.

    Das ist tatsächlich nicht elegant lösbar. Es gibt ein Paper, an dem noch gearbeitet wird: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0082r2.pdf

    Multiple breaks
    The early termination block also provides for graceful multiple breaks. Suppose you want to iterate over a three-dimensional table and choose a particular cell. Today you might write something like this:

    vector<vector<vector<. . .>>> table = . . .;
    for (auto& x : table)
      for (auto& y : x)
        for (auto& z : y)
          if (some_condition(z))
          {
            do_something(z);
            goto DONE;
          }
    DONE:
    

    This isn’t too bad, but it gets worse if you have different exit situations. This particular problem could be solved by putting the loops in a function, then returning from the innermost loop, but not all algorithms are so easily encapsulated. With early termination blocks, you can do this:

    for (auto& x : table)
      for (auto& y : x)
        for (auto& z : y)
          if (some_condition(z))
          {
             do_something(z);
             break;
          }
          on_break break;
        on_break break;
    

    This scales well to more complicated cases since you can either continue or break on either termination condition. I would expect that the compiler could collapse the repeated breaks into a single jump, so the efficiency of the goto solution would be preserved.

    Ich vermute allerdings, dass viele solcher Fälle von verschachtelten "inner-break" for-Schleifen tatsächlich eine nicht verschachtelte, flache Iteration imitieren sollen. Im obigen Fall sollte demnach eine Schleife über eine "flat view" stattfinden. Kennt jemand ein Gegenbeispiel?



  • hustbaer schrieb:

    zufallswert schrieb:

    Ja. Wenn ich in bestimmten Fällen entscheide, dass es eleganter ist, zum clean-up mit goto zu verzweigen statt Exceptions zu nutzen, bedarf dies keinerlei Rechtfertigung oder gar Belehrung. Sorry ... 😃

    Dazu gibt's jetzt nicht viel zu sagen. Wenn du es nicht für nötig befindest irgendwas sachliches zu antworten, dann sage ich einfach mal: nein, da liegst du völlig falsch. Sorry.

    wenn du Streit suchst, hast du bei mir Pech. Ooooh 😞



  • Ich suche keinen Streit. Ich hatte gehofft dass das ganze zu einem sachlichen Argument führt. Du scheinst aber nur daran interssiert zu sein dich über andere lustig zu machen. Auch gut, kann ich nicht ändern.



  • Arcoth schrieb:

    einwegflasche schrieb:

    Daran hatte ich noch gar nicht gedacht. Ich hatte mich aber immer darüber geärgert, dass ich dafür immer Zustandsflags mit schleppen musste.

    Das ist tatsächlich nicht elegant lösbar.

    Tatsächlich ist das Problem in PHP sehr elegant gelöst. Dort kann man break einfach einen Parameter übergeben, für wieviele Schleifenschichten es gilt (ein break(2) beendet eben die Innerste und darauf Folgende). Ich weiß gerade nicht aus dem Kopf, ob das auch für continue gilt, was aber die logische Konsequenz wäre.



  • Zum Thema "double break" (bzw. "multiple break")...
    Man kann hier oft return statt break bzw. goto verwenden. Ist naheliegend und auch keine universelle Lösung, aber sollte IMO trotzdem erwähnt werden.

    Und falls nicht, ist meine Meinung dass ein goto zum Verlassen mehrerer Schleifen meist ganz klar besser ist als zusätzliche Zustandsvariablen einzuführen.

    Wobei ich finde dass hier noch eines meiner Lieblingspatterns erwähnt werden sollte, nämlich das Method-Object Pattern. Dadurch dass man damit lokale Variablen zu Membervariablen machen kann, lässt sich "return statt break" zum Abbrechen von verschachtelten Schleifen in sehr vielen Fällen anwenden, wo es mit "normalen" Methoden/Funktionen ... umständlich werden würde.



  • @ProGoTo
    Naja, also besonders schön finde ich das nicht. Dann noch lieber die Variante wo man bei break ein Label angeben kann - wobei die Einschränkung gilt dass das Label direkt auf das Ende eines "aktiven" Loops folgen muss. Ist im Prinzip das selbe, nur mMn. leichter zu lesen und weniger fehleranfällig.

    Auf continue lässt sich das ganze im Prinzip genau so anwenden, nur dass das Label dann direkt vor dem Ende eines aktiven Loops stehen muss.


  • Mod

    hustbaer schrieb:

    Wobei ich finde dass hier noch eines meiner Lieblingspatterns erwähnt werden sollte, nämlich das Method-Object Pattern. Dadurch dass man damit lokale Variablen zu Membervariablen machen kann, lässt sich "return statt break" zum Abbrechen von verschachtelten Schleifen in sehr vielen Fällen anwenden, wo es mit "normalen" Methoden/Funktionen ... umständlich werden würde.

    Könntest du das erläutern? Ich suche stets nach einer eleganten Lösung für folgendes Problem: Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?



  • SeppJ schrieb:

    Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    Wenn die Rechnung komplex und lange dauert und Ergebnisse nur selten rauskommen:

    throw ComplexCalculationResult(result);
    

    😉

    (Nicht ernst gemeint!)



  • SeppJ schrieb:

    Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    ohne goto mit

    break (bei einer Schleifenebene)
    return (bei mehreren)
    

  • Mod

    wob schrieb:

    Wenn die Rechnung komplex und lange dauert und Ergebnisse nur selten rauskommen:

    throw ComplexCalculationResult(result);
    

    😉

    (Nicht ernst gemeint!)

    Wutz schrieb:

    break (bei einer Schleifenebene)
    return (bei mehreren)
    

    Das sind halt beides die Lösungen, die nicht so optimal sind. Auch wenn wobs Vorschlag nicht ernst gemeint ist, ist das doch genau das, was ich möchte: Beliebig viele Ebenen sauber abwickeln. Aber das ist halt ein Missbrauch der Exceptions und Exceptions sind im throw-Fall auch arg langsam.

    break und return sind ja gerade die Mechanismen, die nicht ausreichen, denn damit komme ich nicht aus tief verschachtelten Rechnungen heraus, außer ich verzichte auf Unterfunktionen und schreibe stattdessen die gesamte Rechnung in einer einzigen Funktion. Was doof ist. Das ist ja gerade das Thema dieses Threads, dass so etwas nicht immer ausreicht. In diesem Fall ist es aber sogar noch schlimmer, weil die Alternative kein goto, sondern ein longjmp ist, was erst recht Spagetticode ist und auch voraussetzt, dass man ohne sauberes Stackaufräumen auskommt.



  • on-topic:
    Mach ´ne Funktion draus. Wenn es mehr als zwei Typen gibt geht auch der ternäre Operator wie ihn Arcoth vorgeschlagen hat nicht mehr. Bzw. wird länger und unleserlich.

    const char* label_name( unsigned int Type )
    {
       if( fdat.type == ft_IIRLP )  return "IIR low-pass"; 
       else                         return "IIR high-pass"; 
    }
    
    Filter * FilterFactory::create( InData_t * inp ) { 
        Filter *    fi = 0; 
        Fdata_t     fdat; // input to the concrete constructor 
    ...                   // copy some data to fdat 
        switch( inp->type ) { 
    ... 
            case  ft_IIRLP: 
            case  ft_IIRHP: 
                create_IIR( inp, &fdat );            // calculate the parameters 
                fdat.inbuf = get_inbuf( fdat.taps ); // request the input buffer 
                fi = new F_iir_df2( &fdat );         // create the filter 
                fi->label( label_name( fdat.type );
                break; 
    ... 
            default: 
                break; 
        } 
        return fi; 
    }
    


  • SeppJ schrieb:

    Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    Ohne jetzt Trollen zu wollen. Es wird immer von einer tief verschachtelten, komplexen Berechnung erzählt. So eine ist mir bisher noch nie vor die Flinte gekommen. Kannst du da mal ein Beispiel aus der echten Welt geben?


  • Mod

    sowas schrieb:

    Ohne jetzt Trollen zu wollen. Es wird immer von einer tief verschachtelten, komplexen Berechnung erzählt. So eine ist mir bisher noch nie vor die Flinte gekommen. Kannst du da mal ein Beispiel aus der echten Welt geben?

    Ja, aber wieso sollte ich mir dermaßen viel Mühe machen, hier anonymisierten Beispielcode zu erstellen? Da säße ich mindestens 40 Minuten dran, bloß um deine Neugierde zu befriedigen. Kannst du dir nicht vorstellen, dass es auch Leute gibt, die Programme schreiben, die wirklich etwas tun?

    Die Kurzvariante, ohne konkreten Code:
    Laufe über eine Menge. Rufe für jedes Element mehrere Funktionen auf, die jeweils wieder über eine andere Funktion von allen anderen Elementen der Menge abhängen. Zähle alle Zahlen zusammen. Nun kann es aber ganz regulär vorkommen, dass eine der Zahlen ∞ ist, d.h. man kann sich auch den Rest der Rechnung sparen, weil klar ist, dass die Summe aller Zahlen auch ∞ sein wird. Daher will man in dem Fall aus der Unterfunktion der Unterfunktion heraus in die Gesamtrechenfunktion springen und dort mit ∞ aussteigen.



  • @ DocShoe

    Mach ´ne Funktion draus.

    Toll, warum bin ich da nicht selber drauf gekommen? Allerdings werde ich das setzen des Labels ans Ende der create Methode setzen. Damit ist denn auch mein eigentliches Ziel erreicht, nämlich den Switch nicht zu lang werden zu lassen.

    Und eine Funktion hat ja auch den Vortiel, dass man alle Verdächtigen zusammen hat und diese bei Änderungen nicht erst suchen muss.

    Im Übrigen finde ich die Meinungen zum Einsatz von goto interessant.



  • einwegflasche schrieb:

    @ DocShoe

    Mach ´ne Funktion draus.

    Toll, warum bin ich da nicht selber drauf gekommen?

    Wahrscheinlich weil du meinen Beitrag (der immerhin der erste in diesem Thread war: https://www.c-plusplus.net/forum/p2544013#2544013 EDIT: der zweite natürlich, wenn man den Kopfbeitrag mitzählt 😉 /EDIT) nicht gelesen hast?

    Wozu schreib ich eigentlich noch Antworten 😕 🤡



  • SeppJ schrieb:

    hustbaer schrieb:

    Wobei ich finde dass hier noch eines meiner Lieblingspatterns erwähnt werden sollte, nämlich das Method-Object Pattern. Dadurch dass man damit lokale Variablen zu Membervariablen machen kann, lässt sich "return statt break" zum Abbrechen von verschachtelten Schleifen in sehr vielen Fällen anwenden, wo es mit "normalen" Methoden/Funktionen ... umständlich werden würde.

    Könntest du das erläutern? Ich suche stets nach einer eleganten Lösung für folgendes Problem: Tief verschachtelte, hochkomplexe Rechnung (die man auch am liebsten in Unterfunktionen aufteilen würde), bei der aber unter gewissen Fällen vorzeitig klar wird, was das Ergebnis sein wird. Wie bekommt man dann schnell und sauber dieses Ergebnis heraus, ohne im Rest der Rechnung Laufzeitkompromisse einzugehen?

    Naja... ich meinte dass man damit "multi level break" innerhalb einer Funktion in ein return verwandeln kann. Nicht über Funktionsgrenzen hinweg.
    Ala

    int Fun(int param)
    {
        Foo localFoo = some init expression;
        Bar localBar = some init expression;
        int accumulator = 0;
        DoSomethingThatNeedsUndoing();
        for (...)
        {
            for (...)
            {
                /* use localFoo, localBar, update accumulator */
                if (some condition)
                    goto done;
            }
        }
    done:
        UndoTheThing();
        return accumulator;
    }
    
    // =>
    
    class FunMethod
    {
    public:
        static int Run(int param)
        {
            FunMethod mo(param);
            return mo.RunImpl();
        }
    
    private:
        FunMethod(int param) :
            m_localFoo(some init expression),
            m_localBar(some init expression)
        {
            DoSomethingThatNeedsUndoing();
        }
    
        ~FunMethod()
        {
            UndoTheThing();
        }
    
        int RunImpl()
        {
            int accumulator = 0;
            for (...)
            {
                for (...)
                {
                    /* use m_localFoo, m_localBar, update accumulator */
                    if (some condition)
                        return accumulator;
                }
            }
        }
    
        Foo m_localFoo;
        Bar m_localBar;
    };
    
    int Fun(int param)
    {
        return FunMethod::Run(param);
    }
    

    Was Performance angeht sollte das soweit relativ neutral sein, vorausgesetzt der Compiler trifft die richtigen Inlining-Entscheidungen. Hilft aber natürlich nicht wenn man die beiden for-Loops aufteilen möchte. Also funktionsübergreifendes multi level break ala Exceptions oder longjmp geht damit auch nicht.

    Natürlich kann man es irgendwie reinbasteln, z.B. könnte man auch accumulator zu nem Member machen, und dann nen weiteren Member bool m_done dazu machen. Und diesen dann wo auch immer es Sinn macht checken. Performance davon sollte OK sein, korrekt vorausgesagte Sprünge sind auf den meisten CPUs quasi gratis, und bis auf den ersten und letzten Durchlauf sollten alle "if done" korrekt predicted werden.

    BTW: Natürlich müssten localFoo und localBar nicht unbedingt in Member verwandelt werden. Kann aber oft praktisch sein, wenn man z.B. innerhalb von RunImpl diverse Hilfsfunktionen aufrufen will, die Zugriff auf localFoo und/oder localBar benötigen. Daher hab' ich mal diese Variante skizziert, und nicht die mit nur lokalen Variablen, wo im Prinzip nur das make/break Paar im ctor/dtor übrigbleibt. Was man nämlich vermutlich meistens genau so gut über ne kleine RAII Hilfsklasse für genau dieses make/break Paar lösen könnte, die von Fun() komplett unabhängig sein kann.


  • Mod

    Schade. Ich hatte auf die geniale Lösung gehofft, an die ich bisher nie gedacht hatte. Zugegeben unwahrscheinlich, da ich recht intensiv nachgedacht und gegoogelt habe. Dann bleibe ich bei dem Flag in der Art des von dir vorgeschlagenen m_done , welches ich jetzt schon benutze. Das ist nämlich in der Tat akzeptabel von der Performance und der Gewinn durch die Abkürzung überwiegt bei weitem diese Kosten.



  • Nicht durchdacht und entsprechend nicht wirklich ernst gemeint: könntest du nicht über boost::context + evtl. einem entsprechenden Context Parameter einen "continuation point" setzen und in deiner Funktion, die du verlassen willst sagen, jetzt will ich zu dem im Context festgelegten Punkt springen?


Anmelden zum Antworten