switch + goto (bzw. utf8)



  • Sehr hilfreich, berniebutt...

    unskilled schrieb:

    meine erste frage sollte offensichtlich sein: gibt es einen besseren weg, der euch einfällt?

    Wie findest du das:

    int x;
    int left = 0;
    x = 2 /*0..4*/;
    
    switch(x)
    {
      case 1:
        foo1();
        break;
      case 2:
        foo2();
        left = 1;
        break;
      case 3:
        foo3();
        left = 2;
        break;
      case 4:
        foo4();
        left = 3;
    } 
    if(left > 2)
      bar(a);
    if(left > 1)
      bar(b);
    if(left > 0)
      bar(c);
    


  • Also ich finde es schlechter lesbar... Wieso soll ich noch Hilfsvariablen anlegen und noch 3 if`s bauen, obwohl es auch einfacher geht?
    Schneller ist es auch auf gar keinen Fall, weniger Zeilen sind es auch nicht... Also (in meinen Augen) nicht mal nen Pseudo-Vorteil ggnüber der label-Variante

    Mir ist bewusst, dass das hier vermutlich nicht ganz objektiv zu beantworten ist - das man goto nach Möglichkeit vermeiden sollte, wollte ich auch nicht anzweifeln.

    bb

    @bernie:
    "undurchschauhbar" ist nur deine Rechtschreibung
    "weil in einigen wenigen Fällen - z.B. bei einer Iteration - es noch Sinn machen kann" -> "weil [es] in einigen wenigen Fällen [...] noch Sinn machen kann" - ganz genau das frag ich doch gerade...
    "Aber bitte nie in einem switch!" Grund?



  • Naja, Performance-technisch wird die goto-Variante sowieso die schnellste sein.
    Aber lesbarer kriegt man das doch auf jeden Fall, in dem man meins z.B. in eine Funktion auslagert:

    ...
    void barLeft(int left)
    {
      if(left > 2)
        bar(a);
      if(left > 1)
        bar(b);
      if(left > 0)
        bar(c); 
    }
    ...
    int x;
    x = 2 /*0..4*/;
    
    switch(x)
    {
      case 1:
        foo1();
        break;
      case 2:
        foo2();
        barLeft(1);
        break;
      case 3:
        foo3();
        barLeft(2);
        break;
      case 4:
        foo4();
        barLeft(3);
        break;
    }
    


  • unskilled schrieb:

    int x;
    x = 2 /*0..4*/;
    
    switch(x)
    {
      case 1:
        foo1();
        break;
      case 2:
        foo2();
        bar(c);
        break;
      case 3:
        foo3();
        bar(b);
        bar(c);
        break;
      case 4:
        foo4();
        bar(1);
        bar(b);
        bar(c);
        break;
    }
    

    Trotzdem hier die Regel "Don't repeat yourself" gebrochen wird würde ich diese Variante eher noch bevorzugen weil die Lesbarkeit im Vergleich zu den anderen Varianten deutlich besser ist. Die erste Variante von unskilled ist ausgesprochen "obsfucated" weil hier case-fallthrough und goto kombiniert benutzt werden -> beides wird als "bad ideas" betrachtet.

    Die Variante von Jockelx mit der zusätzlichen FUnktion ist ebenfalls besser, eben weil sie sowohl auf fallthrough als auch auf goto verzichtet. Dafür ist die Funktion falsch, weil die drei ifs ein anderes verhalten erzeugen als die gotos im Original.

    Jockelx schrieb:

    Naja, Performance-technisch wird die goto-Variante sowieso die schnellste sein.

    -> Premature Optimizing... dont do it. Die goto-variante kann der COmpiler nicht inlinen, die Funktion-Variante schon. Ich würde daher nicht drauf wetten das die goto Variante schneller sei 😉



  • so könnte man es auch ganz aus dem switch rausnehmen 😉

    so hatte ichs ja auch alles schon - aber besser gefallen tuts mir immer noch nicht^^

    @loks:
    naja - mir wäre keine code-duplikation ja fast ein wenig wichtiger - aber "Die erste Variante von unskilled ist ausgesprochen "obsfucated" weil hier case-fallthrough und goto kombiniert benutzt werden -> beides wird als "bad ideas" betrachtet." hab ich zwar noch nicht gehört aber klingt so, als ob du wüsstest, wovon du redest ;o)

    bb



  • Tatsächlich hab ich bei näherem Drüberlesen Unsinn geschrieben. Da sind keine fallthroughs. Andererseits ein Grund mehr für meine obsfucation-Behauptung wenn man mehrfach genau hingucken muß bis man den Code ganz versteht.



  • Also ich verstehe den Sinn auch nicht ganz. Vielleicht hast du ja einfach ein blödes Beispiel genommen.
    Ich würde hier einfach die foo's und bar's trennen oder zumindest umdrehen.
    Sprich z.B bar mit den Parametern aufrufen und dann dort drin die Untescheidung mit switch/case machen.



  • drakon schrieb:

    Also ich verstehe den Sinn auch nicht ganz. Vielleicht hast du ja einfach ein blödes Beispiel genommen.
    Ich würde hier einfach die foo's und bar's trennen oder zumindest umdrehen.
    Sprich z.B bar mit den Parametern aufrufen und dann dort drin die Untescheidung mit switch/case machen.

    ja, kann schon sein, dass das bsp. an sich bissl doof war, aber ich habs jz einfach mit ner zusätzlichen schleife gelöst - wird der compiler schon wegoptimieren können ;o) und wenn nicht, dann ist das eben so^^
    das mit dem nicht inlinen können klang für mich schlimmer ;o)

    die möglichkeit, doppelten code zu nehmen hatte ich relativ schnell wieder verworfen - mir war dann auch wieder eingefallen, wieso ich die schonmal als doof eingeschätzt hatte 😉

    bb



  • @unskilled --> Deine Rückfragen
    Also: In früheren Zeiten war die Benutzung von 'goto' weitverbreitet. Die ersten BASIC-Compiler kannten nicht viel mehr zur Ablaufkontrolle. Hiermit wurde oft sog. Spaghetti-Code erzeugt mit wilden Sprüngen kreuz und quer. Solche Programme waren unübersichtlich und im Fehlerfall schwer nachvollziehbar gewesen. Deshalb gab es in den 1980er Jahren eine Kampagne gegen das 'goto'.Für gewöhnliche Iterationen nimmt man heute besser while-Schleifen.



  • berniebutt schrieb:

    @unskilled --> Deine Rückfragen
    Also: In früheren Zeiten war die Benutzung von 'goto' weitverbreitet. Die ersten BASIC-Compiler kannten nicht viel mehr zur Ablaufkontrolle. Hiermit wurde oft sog. Spaghetti-Code erzeugt mit wilden Sprüngen kreuz und quer. Solche Programme waren unübersichtlich und im Fehlerfall schwer nachvollziehbar gewesen. Deshalb gab es in den 1980er Jahren eine Kampagne gegen das 'goto'.Für gewöhnliche Iterationen nimmt man heute besser while-Schleifen.

    Die Basic-Compiler waren damals üblicherweise Basic-Interpreter.
    Und Aussagen wie "tue etwas nie" oder "tue etwas stets" sind irgendwie mit Vorsicht zu genießen. Es gibt durchaus ein paar Fälle, wo goto sinnvoll ist und die Übersichtlichkeit steigert. Zum Verlassen von tief geschachtelten Schleifen z.B. (sofern die Sprache nicht sowas wie exit LABEL hat, aber das ist schließlich auch nur ein besseres goto).
    Bei dem Teil von unskilled würde ich goto allerdings vermutlich auch nicht benutzen. Vielleicht kann man da etwas mit Funktionsüberladung basteln, dass etwas wartbarer ist.



  • berniebutt schrieb:

    Deshalb gab es in den 1980er Jahren eine Kampagne gegen das 'goto'.

    Kurzfassung:
    1968: Go To Statement Considered Harmful http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF
    über
    1978: With quält die Welt mit Modula II http://de.wikipedia.org/wiki/Modula-2
    bis
    1991: Qbasic verdängt goto-erzwingende Basic-Dialekte http://de.wikipedia.org/wiki/QBasic



  • Ich hatte dieses Problem neulich auch öfters (beim Umbau eines alten Codes).
    Da es eben öfters auftrat, habe ich mich für ein zweites Switch entschieden und dieses Verfahren an allen Stellen angewandt.
    Sieht insgesamt feiner aus, als es hier im ersten Moment erscheint.

    int x;
    int xtype;
    
    x = 2 /*0..4*/;
    
    switch (x)
    {
      case 1: foo1(); xtype = 0; break;
      case 2: foo2(); xtype = 1; break;
      case 3: foo3(); xtype = 2; break;
      case 4: foo4(); xtype = 3; break;
    }
    
    switch (xtype)
    {
      case 3:
        bar(a);
      case 2:
        bar(b);
      case 1:
        bar(c);
    }
    

    Eigentlich würde ich auch einen extra Funktionsaufruf bevorzugen, aber in diesem Fall kam es mir darauf an, dass keine neuen Funktionen entstehen, sondern alles in genau dem Rahmen abgehandelt wird, in dem es original vorlag.

    Prinzipiell könnte 'xtype' sogar x selbst sein, aber ich wollte die Möglichkeit einer Änderung des x im ersten switch offen halten.

    Wird eine richtige Enumerierung eingesetzt, sieht's noch sauberer aus.



  • unskilled schrieb:

    meine erste frage sollte offensichtlich sein: gibt es einen besseren weg, der euch einfällt?

    switch ist eines der überflüssigsten Sprachkonstrukte in C++. Fast immer ist es sinnvoller was anderes zu nehmen.

    #include <iostream>
    
    int main () {
        int x(4); // 0..4
    
        switch(x) {
        case 1:
            break;
        case 2:
            goto one_left;
        case 3:
            goto two_left;
        case 4:
            goto three_left;
    
        three_left:
            std::cout << "three_left\n";
        two_left:
            std::cout << "two_left\n";
        one_left:
            std::cout << "one_left\n";
        }
        std::cout << "end of switch\n\n";
    
        if (1 < x) {
            if (2 < x) {
                if (3 < x) {
                    std::cout << "three_left\n";
                }
                std::cout << "two_left\n";
            }
            std::cout << "one_left\n";
        }
        std::cout << "end of switch\n";
    }
    


  • ~john hier vergleichst du aber äpfel mit birnen...

    if (1 < x) {
            if (2 < x) {
                if (3 < x) {
                    std::cout << "three_left\n";
                }
                std::cout << "two_left\n";
            }
            std::cout << "one_left\n";
        }
        std::cout << "end of switch\n";
    

    vs.

    switch(x)
    {
      case 3:    std::cout << "three_left\n";
      case 2:    std::cout << "two_left\n";
      case 1:    std::cout << "one_left\n";
    }
    std::cout << "end of switch\n";
    

    noch dazu müsste es in deinem code if(x == 3) heißen und nicht if(x > 3) .

    bb



  • unskilled schrieb:

    ~john hier vergleichst du aber äpfel mit birnen...

    if (1 < x) {
            if (2 < x) {
                if (3 < x) {
                    std::cout << "three_left\n";
                }
                std::cout << "two_left\n";
            }
            std::cout << "one_left\n";
        }
        std::cout << "end of switch\n";
    

    vs.

    switch(x)
    {
      case 3:    std::cout << "three_left\n";
      case 2:    std::cout << "two_left\n";
      case 1:    std::cout << "one_left\n";
    }
    std::cout << "end of switch\n";
    

    noch dazu müsste es in deinem code if(x == 3) heißen und nicht if(x > 3) .

    Geht viel schneller und einfacher.

    #include <iostream>
    using namespace std;
    
    int main(){
    	int x;
    	cin>>x;
    	cout<<&x["\35\24\13"]["three_left\ntwo_left\none_left\nend of switch\n"];
    }
    


  • unskilled schrieb:

    meine erste frage sollte offensichtlich sein: gibt es einen besseren weg, der euch einfällt?

    Frage zu speziell. In welchem Kontext passiert das? Sind die Funktionen überhaupt sinnvoll? Ich kann nur über Verbesserungen nchdenken, wenn ich weiß, was da geschehen soll.



  • Da kann ich volkard zustimmen. Das Beispiel sagt nicht was du wirklich machst. Wahrscheinlich kann man es ganz anders machen, aber dazu müsste man erst mal wissen was es macht.



  • @Alle hier
    Die Diskussion bringt nichts mehr. Wir und der Fragesteller kommen da doch zur Frage "Wie entwerfe ich ein übersichtlich strukturiertes, leicht nachvollziehbares und wartungsfreundliches Programm?" Das ist dann ein vollkommen anderes Thema! Hier ging es um den Gebrauch von 'goto'. Und diesen sollte man wirklich nur dann einsetzen, wenn es anders unübersichtlicher bleibt. Auch ich setze gelegentlich deswegen 'goto' ein, was bei verschachtelten Schleifen sogar Sinn macht. Das lässt sich aber nur aus der konkreten Aufgabenstellung klären, die hier unbekannt ist.



  • ging um Zeichenkonvertierung: utf8 -> ucs4.
    So siehts jetzt aus:

    template <typename InputIterator>
    static ucs4 get_ucs4_from_utf8(InputIterator &iter, InputIterator end)
    {
    	unsigned char tmp = unsigned char(*iter++);
    	const unsigned char length = get_length(tmp);
    
    	switch(length)
    	{
    	case 1:
    		return ucs4(tmp);
    	case 2:
    		tmp = null_first_three_bits(tmp);
    		break;
    	case 3:
    		tmp = null_first_four_bits(tmp);
    		break;
    	case 4:
    		tmp = null_first_five_bits(tmp);
    		break;
    	}
    
    	ucs4 to_add = make_byte(tmp, --length);
    
    	for(; length >= 0; --length)
    	{
    		if(iter == end)
    			throw std::runtime_error("wrong utf8 encoding");
    		to_add = add(to_add, *iter++, length);
    	}
    
    	return to_add;
    }
    

    die Schleife wollte ich halt eigtl mit in das switch bauen, aber habs jetzt so gemacht, dass make_byte eben das erste byte(erster parameter) (um 2. parameter * 6) nach links shiftet und add den zweiten parameter um length*6 nach links shiftet und dann den ersten Parameter mit dem geshifteten addiert(bzw. nur nen or ausführt, weil dort ja eh noch 0en sind).
    wenn ich noch ma drüber nachdenke, könnte man auch wirklich nen zweites switch bauen... hmm...

    bb



  • volkard schrieb:

    Geht viel schneller und einfacher.

    Natürlich muß man entsprechend Code einfügen und dann klappt das, was Du hier vorschlägst nicht mehr.


Anmelden zum Antworten