wake-up-neo.com

So vermeiden Sie die Verwendung von goto und brechen verschachtelte Schleifen effizient

Ich würde sagen, es ist eine Tatsache, dass die Verwendung von goto bei der Programmierung in C/C++ als schlechte Praxis gilt.

Allerdings den folgenden Code gegeben

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

Ich frage mich, wie man das gleiche Verhalten effizient erreichen kann, ohne goto zu verwenden. Ich meine damit, dass wir zum Beispiel am Ende jeder Schleife condition prüfen könnten, aber AFAIK wird nur eine Assembly-Anweisung erzeugen, die eine jmp ist. Dies ist also die effizienteste Methode, die mir dabei einfällt.

Gibt es eine andere, die als bewährte Praxis gilt? Bin ich falsch, wenn ich sage, dass es eine schlechte Praxis ist, goto zu verwenden? Wenn ja, wäre dies einer der Fälle, in denen es gut ist, es zu benutzen?

Vielen Dank

30
rual93

Die (imo) beste Nicht-Goto-Version würde ungefähr so ​​aussehen:

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 */
            }
        }
    }
}

Das Aufteilen ist wahrscheinlich auch eine gute Idee, da es Ihnen dabei hilft, Ihre Funktionen kurz zu halten, Ihren Code lesbar zu machen (wenn Sie die Funktionen besser benennen als ich) und Abhängigkeiten gering sind.

47
Max Langhof

Wenn Sie haben tief verschachtelte Schleifen wie diese und Sie müssen ausbrechen, ich glaube, dass goto ist die beste Lösung. Einige Sprachen (nicht C) haben eine break(N) -Anweisung, die aus mehr als einer Schleife besteht. Der Grund, warum C es nicht hat, ist, dass es sogar schlimmer ist als ein goto: Sie müssen die verschachtelten Schleifen zählen, um herauszufinden Was es macht, und es ist anfällig für jemanden, der später mitkommt und eine Verschachtelungsebene hinzufügt oder entfernt, ohne zu bemerken, dass die Anzahl der Pausen angepasst werden muss.

Ja, gotos sind im Allgemeinen verpönt. Die Verwendung von goto ist hier keine gute Lösung. Es ist nur das geringste Übel.

In den meisten Fällen müssen Sie aus einer tief verschachtelten Schleife ausbrechen, weil Sie nach etwas suchen und es gefunden haben. In diesem Fall (und wie mehrere andere Kommentare und Antworten vorgeschlagen haben) ziehe ich es vor, die verschachtelte Schleife in ihre eigene Funktion zu verschieben. In diesem Fall erledigt ein return aus der inneren Schleife Ihre Aufgabe sehr sauber.

(Es gibt Leute, die sagen, dass Funktionen immer am Ende zurückkehren müssen, nicht von der Mitte. Diese Leute würden sagen, dass die einfache Lösung zum Ausbrechen in eine Funktion daher ungültig ist, und sie würden die Verwendung erzwingen Ich persönlich glaube, diese Leute sind falsch, aber Ihre Laufleistung kann variieren.)

Wenn Sie darauf bestehen, kein goto zu verwenden, und wenn Sie darauf bestehen, keine separate Funktion mit einer vorzeitigen Rückkehr zu verwenden, können Sie so etwas wie zusätzliche boolesche Steuervariablen verwalten und diese redundant in der Steuerbedingung testen jeder verschachtelten Schleife, aber das ist nur ein Ärgernis und ein Chaos. (Es ist eines der größten Übel, von dem ich sagte, dass die Verwendung eines einfachen goto geringer ist als.)

24
Steve Summit

Ich denke, dass goto eine absolut gesunde Sache ist, die hier zu tun ist, und eine der außergewöhnlichen Anwendungsfälle gemäß den C++ - Kernrichtlinien ist.

Eine andere zu betrachtende Lösung ist jedoch möglicherweise ein IIFE Lambda. Meiner Meinung nach ist dies etwas eleganter als die Angabe einer separaten Funktion!

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

Dank an JohnMcPineapple on reddit für diesen Vorschlag!

15
ricco19

In diesem Fall vermeiden Sie not die Verwendung von goto.

Im Allgemeinen Die Verwendung von goto sollte vermieden werden, es gibt jedoch Ausnahmen von dieser Regel, und Ihr Fall ist ein gutes Beispiel für eine davon.

Schauen wir uns die Alternativen an:

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;
}

Oder:

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;
            ...
        }
    }
}

Keines davon ist so präzise oder lesbar wie die goto-Version.

Die Verwendung einer goto wird als akzeptabel angesehen, wenn Sie nur vorwärts springen (nicht rückwärts) und dadurch Ihren Code lesbarer und verständlicher machen.

Wenn Sie dagegen goto verwenden, um in beide Richtungen zu springen, oder um in einen Bereich zu springen, der die Initialisierung der Variablen möglicherweise umgehen könnte, wäre das schlecht.

Hier ist ein schlechtes Beispiel für 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);
}

Ein C++ - Compiler gibt hier einen Fehler aus, da die goto die Initialisierung von y überspringt. C-Compiler kompilieren dies jedoch, und der obige Code ruft undefined Verhalten auf, wenn versucht wird, y zu drucken, was bei Auftreten der goto nicht initialisiert wird.

Ein weiteres Beispiel für die korrekte Verwendung von goto ist die Fehlerbehandlung:

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);
}

Dadurch wird die gesamte Bereinigung am Ende der Funktion ausgeführt. Vergleichen Sie dies mit der 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);
}

Wo die Bereinigung an mehreren Stellen erfolgt. Wenn Sie später weitere Ressourcen hinzufügen, die bereinigt werden müssen, müssen Sie daran denken, die Bereinigung an all diesen Stellen sowie die Bereinigung der zuvor abgerufenen Ressourcen hinzuzufügen.

Das obige Beispiel ist relevanter für C als für C++, da Sie im letzteren Fall Klassen mit geeigneten Destruktoren und intelligenten Zeigern verwenden können, um eine manuelle Bereinigung zu vermeiden.

12
dbush

Mit Lambdas können Sie lokale Bereiche erstellen:

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

wenn Sie auch die Möglichkeit haben möchten, return aus diesem Bereich herauszuholen:

if (auto r = [&]()->boost::optional<RetType>{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return {};
        ...
      }
    }
  }
}()) {
  return *r;
}

wo zurück {} oder boost::nullopt ist ein "Umbruch", und das Zurückgeben eines Werts gibt einen Wert aus dem umschließenden Bereich zurück.

Ein anderer Ansatz ist:

for( auto idx : cube( {0,N}, {0,N}, {0,N} ) {
  auto i = std::get<0>(idx);
  auto j = std::get<1>(idx);
  auto k = std::get<2>(idx);
}

wo wir eine iterable über alle 3 Dimensionen generieren und es eine 1 tief verschachtelte Schleife machen. Jetzt funktioniert break einwandfrei. Sie müssen cube schreiben.

In c ++ 17 wird dies

for( auto[i,j,k] : cube( {0,N}, {0,N}, {0,N} ) ) {
}

was nett ist.

In einer Anwendung, in der Sie ansprechbar sein sollen, ist es häufig eine schlechte Idee, eine Schleife über einen großen dreidimensionalen Bereich auf der Ebene des primären Kontrollflusses durchzuführen. Sie können es abfädeln, aber selbst dann haben Sie das Problem, dass der Thread zu lange läuft. Und die meisten dreidimensionalen großen Iterationen, mit denen ich gespielt habe, können von der Verwendung von Sub-Task-Threading selbst profitieren.

Zu diesem Zweck müssen Sie Ihren Vorgang anhand der Art der Daten, auf die zugegriffen wird, kategorisieren und ihn dann an einen Vorgang übergeben, bei dem die Iteration geplant ist für Sie.

auto work = do_per_Voxel( volume,
  [&]( auto&& Voxel ) {
    // do work on the Voxel
    if (condition)
      return Worker::abort;
    else
      return Worker::success;
  }
);

dann geht der Kontrollfluss in die do_per_Voxel Funktion.

do_per_Voxel wird keine einfache nackte Schleife sein, sondern ein System zum Umschreiben der Aufgaben pro Voxel in Aufgaben pro Scanlinie (oder sogar pro Ebene, je nachdem, wie groß die Ebenen/Scanlinien zur Laufzeit sind (!) ) dann senden Sie sie der Reihe nach an einen vom Thread-Pool verwalteten Task-Scheduler, heften die resultierenden Task-Handles zusammen und geben ein zukunftsorientiertes work zurück, auf das gewartet werden kann oder das als Fortsetzungsauslöser für die Beendigung der Arbeit verwendet werden kann .

Und manchmal benutzt man einfach goto. Oder Sie lösen Funktionen für Teilschleifen manuell auf. Oder Sie verwenden Flags, um aus einer tiefen Rekursion auszubrechen. Oder Sie setzen die gesamte 3-Ebenen-Schleife in eine eigene Funktion. Oder Sie komponieren die Schleifenoperatoren mithilfe einer Monadenbibliothek. Oder Sie können eine Ausnahme (!) Auslösen und abfangen.

Die Antwort auf fast jede Frage in c ++ lautet "es kommt darauf an". Der Umfang des Problems und die Anzahl der verfügbaren Techniken ist groß, und die Details des Problems ändern die Details der Lösung.

Alternative - 1

Sie können so etwas tun:

  1. Setze eine bool Variable am Anfang isOkay = true
  2. Alle Ihre forloop condition, fügen Sie eine zusätzliche Bedingung hinzu isOkay == true
  3. Wenn Ihre benutzerdefinierte Bedingung erfüllt/fehlgeschlagen ist, legen Sie isOkay = false fest. 

Dadurch werden Ihre Loops angehalten. Eine zusätzliche Variable bool wäre jedoch manchmal praktisch.

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

Zweitens. Wenn sich die obigen Schleifeniterationen in einer Funktion befinden, ist die beste Wahl return, wenn die benutzerdefinierte Bedingung erfüllt ist.

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;
}
4
JeJo

Teilen Sie Ihre for-Loops in Funktionen auf .. _. Sie machen die Dinge deutlich verständlicher, da Sie jetzt sehen, was die einzelnen Loops tatsächlich tun.

bool doHerpDerp() {
    for (i = 0; i < N; ++i) 
    {
        if (!doDerp())
            return false;
    }
    return true;
}

bool doDerp() {
    for (int i=0; i<X; ++i) {
        if (!doHerp())
            return false;
    }
    return true;
}

bool doHerp() {
    if (shouldSkip)
        return false;
    return true;
}
3
UKMonkey

Gibt es eine andere, die als bewährte Praxis gilt? Bin ich falsch wenn Ich sage, dass es eine schlechte Praxis ist, goto zu benutzen?

goto kann missbraucht und überlastet werden, aber ich sehe keine der beiden in Ihrem Beispiel. Das Ausbrechen einer tief verschachtelten Schleife wird am deutlichsten durch einen einfachen goto label_out_of_the_loop; ausgedrückt. 

Es ist eine schlechte Praxis, viele gotos zu verwenden, die zu verschiedenen Bezeichnungen springen, aber in solchen Fällen ist es nicht das Schlüsselwort goto selbst, das den Code fehlerhaft macht. Es ist die Tatsache, dass Sie im Code herumspringen, was es schwierig macht, dem Code zu folgen. Wenn Sie jedoch einen einzigen Sprung aus verschachtelten Schleifen benötigen, können Sie das Werkzeug verwenden, das genau für diesen Zweck erstellt wurde.

Um eine aus der Luft zusammengesetzte Analogie zu verwenden: Stellen Sie sich vor, Sie leben in einer Welt, in der es früher hippe war, Nägel in Wände zu schlagen. In letzter Zeit wurde es immer beliebter, Schrauben mit Hilfe von Schraubenziehern in Wände zu bohren, und Hammer ist völlig aus der Mode. Bedenken Sie, dass Sie (obwohl Sie ein bisschen altmodisch sind) einen Nagel in die Wand bekommen müssen. Sie sollten es nicht unterlassen, einen Hammer zu verwenden, aber vielleicht sollten Sie sich eher fragen, ob Sie wirklich einen Nagel in der Wand anstelle einer Schraube brauchen. 

(Nur für den Fall, dass es nicht klar ist: Der Hammer ist goto und der Nagel in der Wand ist ein Sprung aus einer verschachtelten Schleife, während die Schraube in der Wand Funktionen verwendet, um das tiefe Verschachteln insgesamt zu vermeiden;)

3
user463035818

Spezifisch

IMO, in diesem speziellen Beispiel halte ich es für wichtig, eine gemeinsame Funktionalität zwischen Ihren Schleifen zu erkennen. (Jetzt weiß ich, dass Ihr Beispiel hier nicht unbedingt wörtlich ist, aber halten Sie mich einfach für eine Sekunde.) Da jede Schleife N durchläuft, können Sie Ihren Code folgendermaßen umstrukturieren:

Beispiel

int max_iterations = N * N * N;
for (int i = 0; i < max_iterations; i++)
{
    /* do stuff, like the following for example */
    *(some_ptr + i) = 0; // as opposed to *(some_3D_ptr + i*X + j*Y + Z) = 0;
    // some_arr[i] = 0; // as oppose to some_3D_arr[i][j][k] = 0;
}

Nun, es ist wichtig zu wissen, dass alle Loops, ob für oder auf andere Weise, wirklich nur synthetischer Zucker für das if-goto-Paradigma sind. Ich stimme mit den anderen überein, dass Sie über eine Funktion verfügen sollten, die das Ergebnis zurückgibt. Ich wollte jedoch ein Beispiel wie das obige zeigen, bei dem dies möglicherweise nicht der Fall ist. Zugegeben, ich würde das Obige in einer Codeüberprüfung kennzeichnen, aber wenn Sie das Obige durch ein Goto ersetzen, würde ich dies als einen Schritt in die falsche Richtung betrachten. (HINWEIS - Stellen Sie sicher, dass Sie es zuverlässig in Ihren gewünschten Datentyp einfügen können.)

Allgemeines

Als allgemeine Antwort sind die Exit-Bedingungen für Ihre Schleife möglicherweise nicht immer die gleichen (wie der betreffende Beitrag). Ziehen Sie in der Regel so viele nicht benötigte Operationen aus Ihren Schleifen (Multiplikationen usw.) heraus, wie Sie können, da Compiler jeden Tag intelligenter werden, und es gibt keinen Ersatz für das Schreiben von effizientem und lesbarem Code. 

Beispiel

/* matrix_length: int of m*n (row-major order) */
int num_squared = num * num;
for (int i = 0; i < matrix_length; i++)
{
    some_matrix[i] *= num_squared; // some_matrix is a pointer to an array of ints of size matrix_length
}

anstatt *= num * num zu schreiben, müssen wir uns nicht mehr auf den Compiler verlassen, um dies für uns zu optimieren (obwohl jeder gute Compiler dies tun sollte). Jede doppelt oder dreifach verschachtelte Schleife, die die obige Funktionalität ausführt, würde also nicht nur Ihrem Code zugute kommen, sondern auch der IMO, die sauberen und effizienten Code auf Ihrer Seite schreibt. Im ersten Beispiel hätten wir stattdessen *(some_3D_ptr + i*X + j*Y + Z) = 0;! Vertrauen wir dem Compiler, i*X und j*Y zu optimieren, damit sie nicht bei jeder Iteration berechnet werden?

bool check_threshold(int *some_matrix, int max_value)
{
    for (int i = 0; i < rows; i++)
    {
        int i_row = i*cols; // no longer computed inside j loop unnecessarily.
        for (int j = 0; j < cols; j++)
        {
            if (some_matrix[i_row + j] > max_value) return true;
        }
    }
    return false;
}

Yuck! Warum verwenden wir keine Klassen, die von der STL oder einer Bibliothek wie Boost bereitgestellt werden? (Wir müssen hier einen Low-Level-/Hochperformance-Code ausführen). Ich konnte aufgrund der Komplexität nicht einmal eine 3D-Version schreiben. Obwohl wir etwas von Hand optimiert haben, ist es möglicherweise sogar besser, die Option #pragma rollen oder ähnliche Präprozessor-Hinweise zu verwenden, sofern Ihr Compiler dies zulässt.

Fazit

Je höher der Abstraktionsgrad, den Sie verwenden können, desto besser. Im Allgemeinen lohnt es sich jedoch, den Codefluss schwieriger zu verstehen/zu erweitern, wenn Sie sagen, dass eine 1-dimensionale Reihenmatrix von Ganzzahlen in ein 2-dimensionales Array gefälscht wird ? Ebenso kann dies ein Indikator sein, um etwas zu seiner eigenen Funktion zu machen. Ich hoffe, dass Sie anhand dieser Beispiele sehen können, dass unterschiedliche Paradigmen an verschiedenen Orten erforderlich sind, und es ist Ihre Aufgabe als Programmierer, das herauszufinden. Machen Sie sich nicht verrückt mit dem oben genannten, aber stellen Sie sicher, dass Sie wissen, was sie bedeuten, wie sie verwendet werden sollen, und wenn sie dazu aufgefordert werden, und vor allem, stellen Sie sicher, dass die anderen Benutzer, die Ihre Codebase verwenden, wissen, was sie sind und welche haben keine Bedenken darüber. Viel Glück!

2
jfh

ihr Kommentar zur Effizienz, wenn Sie die beiden Optionen im Release-Modus auf Visual Studio 2017 zusammenstellen, erzeugt exakt dieselbe Assembly. 

for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                goto end;

            }
        }
    }
}
end:;

und mit einer Fahne.

bool done = false;
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                done = true;
                break;
            }
        }
        if (done) break;
    }
    if (done) break;
}

beide produzieren ..

xor         edx,edx  
xor         ecx,ecx  
xor         eax,eax  
cmp         edx,1  
jne         main+15h (0C11015h)  
cmp         ecx,2  
jne         main+15h (0C11015h)  
cmp         eax,3  
je          main+27h (0C11027h)  
inc         eax  
cmp         eax,5  
jl          main+6h (0C11006h)  
inc         ecx  
cmp         ecx,5  
jl          main+4h (0C11004h)  
inc         edx  
cmp         edx,5  
jl          main+2h (0C11002h)    

also gibt es keinen Gewinn. Wenn Sie einen modernen C++ - Compiler verwenden, können Sie ihn auch in ein Lambda packen.

[](){
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                return;
            }
        }

    }
}
}();

dies erzeugt wiederum genau dieselbe Baugruppe. Ich persönlich denke, dass die Verwendung von goto in Ihrem Beispiel durchaus akzeptabel ist. Es ist klar, was mit jedem anderen passiert, und macht den Code prägnanter. Das Lambda ist wohl ebenso prägnant.

2
rmawatson

Es gibt bereits einige hervorragende Antworten, die Ihnen zeigen, wie Sie Ihren Code umgestalten können. Ich werde sie daher nicht wiederholen. Es ist nicht mehr erforderlich, auf diese Weise für die Effizienz zu codieren. Die Frage ist, ob es nicht wichtig ist. (Okay, eine Verfeinerung, ich schlage vor: Wenn Ihre Hilfsfunktionen immer nur innerhalb des Körpers dieser einen Funktion verwendet werden sollen, können Sie den Optimierer unterstützen, indem Sie sie als static deklarieren, sodass er sicher weiß, dass die Funktion dies nicht tut haben eine externe Verknüpfung und werden niemals von einem anderen Modul aus aufgerufen, und der Hinweis inline kann nicht schaden. In früheren Antworten wird jedoch darauf hingewiesen, dass moderne Compiler bei Verwendung eines Lambda solche Hinweise nicht benötigen.)

Ich werde den Rahmen der Frage etwas herausfordern. Es stimmt, dass die meisten Programmierer ein Tabu gegen die Verwendung von goto haben. Dies hat meiner Meinung nach den ursprünglichen Zweck aus den Augen verloren. Als Edsger Dijkstra schrieb: "Gehe zu Aussage als schädlich", gab es einen bestimmten Grund, an den er dachte: Die "ungezügelte" Verwendung von go to macht es zu schwierig, formell über den aktuellen Programmzustand zu urteilen, und welche Bedingungen aktuell erfüllt sein müssen. verglichen mit dem Kontrollfluss von rekursiven Funktionsaufrufen (die er bevorzugt hat) oder iterativen Schleifen (die er akzeptierte). Er schloss:

Die go to-Anweisung in ihrer derzeitigen Form ist einfach zu primitiv. Es ist zu viel eine Einladung, ein Programm durcheinander zu bringen. Man kann die Klauseln, die als überbrückend angesehen werden, betrachten und würdigen. Ich behaupte nicht, dass die genannten Klauseln in dem Sinne erschöpfend sind, dass sie alle Bedürfnisse erfüllen, aber welche Klauseln vorgeschlagen werden (z. B. Abtreibungsklauseln), sie sollten die Anforderung erfüllen, dass ein vom Programmierer unabhängiges Koordinatensystem aufrechterhalten werden kann, um den Prozess in einem System zu beschreiben hilfreiche und überschaubare Weise. 

Viele C-ähnliche Programmiersprachen, z. B. Rust und Java, haben eine zusätzliche "Klausel, die als überbrückende Verwendung betrachtet wird", die Variable break für ein Label. Eine noch engere Syntax könnte so etwas wie break 2 continue; sein, um zwei Ebenen der verschachtelten Schleife zu durchbrechen und am Anfang der Schleife, die sie enthält, wieder aufzunehmen. Dies stellt kein größeres Problem dar als eine break im C-Stil, was Dijkstra tun wollte: Definieren einer kurzen Beschreibung des Programmzustands, den Programmierer im Kopf nachverfolgen können, oder ein statischer Analysator könnte dies finden.

Durch die Beschränkung von goto auf solche Konstruktionen wird es einfach zu einem umbenannten Bruch in ein Label. Das verbleibende Problem dabei ist, dass der Compiler und der Programmierer nicht unbedingt wissen, dass Sie ihn nur so verwenden werden.

Wenn es eine wichtige Nachbedingung gibt, die nach der Schleife gilt, und Ihre Sorge um goto die gleiche ist wie die von Dijkstra, könnten Sie sie in einem kurzen Kommentar angeben, etwa // We have done foo to every element, or encountered condition and stopped.. Dies würde das Problem für den Menschen lindern, und ein statischer Analysator sollte dies tun gut machen.

1
Davislor

Eine Möglichkeit besteht darin, einer Variablen, die den Status darstellt, einen booleschen Wert zuzuweisen. Dieser Zustand kann später mit einer bedingten "IF" -Anweisung für andere Zwecke im Code getestet werden.

1
Chigozie Orunta
bool meetCondition = false;
for (i = 0; i < N && !meetCondition; ++i) 
{
    for (j = 0; j < N && !meetCondition; j++) 
    {
        for (k = 0; k < N && !meetCondition; ++k) 
        {
            ...
            if (condition)
                meetCondition = true;
            ...
        }
    }
}
1
nameless1

Die beste Lösung ist, die Schleifen in eine Funktion zu setzen und dann return von dieser Funktion. 

Dies ist im Wesentlichen das gleiche wie Ihr goto-Beispiel, jedoch mit dem massiven Nutzen , das Sie vermeiden, eine weitere goto-Debatte zu führen.

Vereinfachter Pseudo-Code:

bool function (void)
{
  bool result = something;


  for (i = 0; i < N; ++i) 
    for (j = 0; j < N; j++) 
      for (k = 0; k < N; ++k) 
        if (condition)
          return something_else;
  ...
  return result;
}

Ein weiterer Vorteil ist, dass Sie ein Upgrade von bool auf enum durchführen können, wenn Sie auf mehr als 2 Szenarien stoßen. Mit goto kann man das nicht wirklich lesbar machen. In dem Moment, in dem Sie mit mehreren Gotos und mehreren Labels beginnen, können Sie die Spaghetti-Codierung verwenden. Ja, auch wenn Sie nur nach unten verzweigen - es ist nicht schön zu lesen und zu warten.

Wenn Sie drei verschachtelte for-Schleifen haben, kann dies ein Hinweis darauf sein, dass Sie versuchen sollten, Ihren Code in mehrere Funktionen aufzuteilen, und dann ist diese ganze Diskussion möglicherweise nicht relevant.

0
Lundin