switch + goto (bzw. utf8)



  • 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.



  • unskilled schrieb:

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

    Du liest nicht aufmerksam, das ist alles. Es gibt exakt das gleiche Ergebnis heraus! Und natürlich muß man anstelle der sinnfreien Ausgaben entsprechende Codeblöcke einfügen.



  • while ist auch komplett sinnlos, oder?

    else ist auch total sinnlos

    ...



  • berniebutt schrieb:

    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.

    Aber die verschachtelten Schleifen machen keinen Sinn, womit das goto wieder stirbt. 🕶



  • was machen null_first_three_bits und so? den code müßte ich erst entoptimieren.



  • der ist 0 optimiert, sondern erst mal nur so geschrieben, dass er läuft und verständlich ist...

    was machen null_first_three_bits und so?

    3mal darfst du raten ;o)

    es setzt die ersten 3 bit auf 0

    static unsigned char null_first_three_bits(unsigned char value)
    {// 0001 1111
    	return value & 0x1F;
    }
    

    Der Rest analog dazu... (ich find nur ein tmp & 0x1F so extrem nichtssagend deshalb verfrachte ich so was immer in extra fkt - wenn man binäre schreiben könnte und nicht nur hexadezimal wär das scho cool...)
    ucs4 ist nen typedef auf unsigned long

    das wars eigtl, oder?

    bb



  • unskilled schrieb:

    es setzt die ersten 3 bit auf 0

    static unsigned char null_first_three_bits(unsigned char value)
    {// 0001 1111
    	return value & 0x1F;
    }
    

    Der Rest analog dazu... (ich find nur ein tmp & 0x1F so extrem nichtssagend deshalb verfrachte ich so was immer in extra fkt

    Aha.
    Also wirds zu

    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);
    
    	if (length==0)
    		return ucs4(tmp);
    
    	tmp = tmp & (0xef>>length);
    
    	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;
    }
    

    (ungetestet)



  • ich hasse ja eigtl so nen bit-gefrickel in meinem quellcode...^^

    aber die idee hier
    tmp = tmp & (0xef>>length);
    ist klasse - danke! : >

    die schleife am ende würdest du lassen?!
    oder durch nen switch ersetzen?
    oder was ganz anderes?

    bb



  • unskilled schrieb:

    ich hasse ja eigtl so nen bit-gefrickel in meinem quellcode...^^

    aber die idee hier
    tmp = tmp & (0xef>>length);
    ist klasse - danke! : >

    die schleife am ende würdest du lassen?!
    oder durch nen switch ersetzen?
    oder was ganz anderes?

    bb

    ja und nein.
    ich hab mal ganz neu angesetzt und die fehlerprüfing rausgemacht.
    (bitte nicht erschrecken!)

    template <typename InputIterator>
    static ucs4 get_ucs4_from_utf8(InputIterator &iter, InputIterator end)
    {
    	ucs4 result=*iter++;
    	if(result>128)
    		return result;
              maske<<=1;
    	result=(result<<6)|(*iter++&0x5f);
    	if(result&maske) return result&...;
              maske<<=1;
    	result=(result<<6)|(*iter++&0x5f);
    	if(result&maske) return result&...;
              maske<<=1;
    	result=(result<<6)|(*iter++&0x5f);
    	return result&maske;
    }
    

    Das sieht danach aus, als könnte man ne schleife draus machen. und dann wieder mit der fehlerprüfung rein.
    und zur größten not könnte man die masken so schreiben

    const ucs4 B32_00000000_00000000_00000000_00001111=0x0000000f;
    const unsigned char B8_011111111=127;
    

    oder vielleicht leckerer keine schleife aber immer

    if(iter==end) goto fehler
    

    um den throw-code nur einmal zu haben. klingt schnell und nicht zu kompliziert. dann ich nicht mehr das doofe maske<<=1.



  • unskilled schrieb:

    while ist auch komplett sinnlos, oder?

    Ich sehe ich habe es mit einem Liebhaber des "switch" Konstrukts zu tun. "switch" ist vorallem in C sinnvoll, da es dort häufig notwendig ist, Integer als Typid zu benutzen, und manuell Dispatching nachbauen muß. Das entfällt in sauberem C++ komplett. Es bleiben wenige Fälle übrig in denen es sinnvoll ist, in C++ switch zu verwenden. Genauso wenig kann man auf "goto" immer verzichten, das Verlassen mehrfach verschachtelter schleifen ist sonst zu umständlich. Aber das dürfte eine der wenigen sinnvollen Anwendungsfälle sein.

    Im konkreten Fall ist switch definitiv nicht sinnvoll. Die vielen "goto"s sind mehr als deutlich ein Hinweis auf schlechtes Design.

    P.S. Wenn man schon ein Schleifenkonstrukt abschaffen will, weil es nicht orthogonal zu anderen Sprachkonstrukten ist, dann nimmt man besser die "for"-Schleife: siehe Oberon.



  • @volkard - uff, darüber schlaf ich erst mal ne nacht 😉 danke 🙂

    bb


Anmelden zum Antworten