Użyć GOTO czy nie?

Obecnie pracuję nad projektem, w którym polecenia goto są nagłówkowo używane. Głównym celem instrukcji goto jest posiadanie jednej sekcji czyszczenia w rutynie, a nie wielu instrukcji return. Jak poniżej:

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
     goto Exit;
   }

   // Lot of code...

Exit:
   if(p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

To znacznie ułatwia, ponieważ możemy śledzić nasz kod czyszczenia w jednej sekcji kodu, czyli po etykiecie wyjścia.

Jednak, czytałem wiele miejsc to zła praktyka, aby mieć Goto wypowiedzi.

Obecnie czytam Kod Complete Książka, i mówi, że musimy używać zmiennych zbliżonych do ich deklaracji. Jeżeli użyjemy goto to musimy zadeklarować / zainicjalizować wszystkie zmienne przed pierwszym użyciem goto w przeciwnym razie kompilator poda błędy, że inicjalizacja zmiennej xx jest pomijana przez polecenie goto.

Którędy jest właściwe?


Z komentarza Scotta:

Wygląda na to, że używanie goto do przeskakiwania z jednej sekcji do drugiej jest złe, ponieważ utrudnia odczyt kodu i Rozumiem.

Ale jeśli używamy goto tylko do przodu i do jednej etykiety, to powinno być dobrze(?).

Author: Alien01, 2008-12-18

30 answers

Nie jestem pewien, co masz na myśli mówiąc czyszczenie kodu, ale w C++ istnieje koncepcja o nazwie "pozyskiwanie zasobów jest inicjalizacją " i to powinno być obowiązkiem Twoich destruktorów, aby wyczyścić rzeczy.

W C# i Javie zwykle rozwiązuje się to poprzez try / finally)

Aby uzyskać więcej informacji, sprawdź tę stronę: http://www.research.att.com/~BS / bs_faq2. html # finally

EDIT : wyjaśnię to trochę.

Rozważmy następujący kod:

void MyMethod()
{
    MyClass *myInstance = new MyClass("myParameter");
    /* Your code here */
    delete myInstance;
}

Problem: co się stanie, jeśli masz wiele wyjść z funkcji? Musisz śledzić każde wyjście i usuwać obiekty przy wszystkich możliwych wyjściach! W przeciwnym razie będziesz miał wycieki pamięci i zasoby zombie, prawda?

Rozwiązanie: zamiast tego użyj odniesień do obiektów, ponieważ są one czyszczone automatycznie, gdy kontrolka opuści zakres.

void MyMethod()
{
    MyClass myInstance("myParameter");
    /* Your code here */
    /* You don't need delete - myInstance will be destructed and deleted
     * automatically on function exit */
}

O tak, i używać std::unique_ptr lub coś podobnego, ponieważ przykład powyżej jest oczywiście niedoskonały.

 58
Author: Tamas Czinege,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 14:23:12

Nigdy nie musiałem używać goto w C++. Nigdy. Nigdy. Jeśli jest taka sytuacja, powinna być używana, jest niezwykle rzadka. Jeśli rzeczywiście rozważasz uczynienie goto standardową częścią twojej logiki, coś wyleciało z torów.

 59
Author: Gene Roberts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:03:08

Są w zasadzie dwa punkty, które ludzie robią w odniesieniu do gotos i Twojego kodu:

  1. Goto jest złe.Bardzo rzadko spotyka się miejsce, w którym potrzebujesz gotos, ale nie sugerowałbym, aby uderzyć go całkowicie. Chociaż C++ ma wystarczająco inteligentny przepływ sterowania, aby goto rzadko było odpowiednie.

  2. Twój mechanizm oczyszczania jest zły: ten punkt jest o wiele ważniejszy. W C samodzielne zarządzanie pamięcią jest nie tylko OK, ale często najlepszy sposób na rzeczy. W C++ twoim celem powinno być unikanie zarządzania pamięcią w jak największym stopniu. Należy unikać zarządzania pamięcią Tak bardzo, jak to możliwe. Niech kompilator zrobi to za Ciebie. Zamiast używać new, wystarczy zadeklarować zmienne. Tylko wtedy, gdy naprawdę potrzebujesz zarządzania pamięcią, nie znasz z góry rozmiaru swoich danych. Nawet wtedy powinieneś spróbować użyć tylko niektórych kolekcji STL.

W przypadku, gdy zgodnie z prawem potrzebujesz pamięci zarządzanie (tak naprawdę nie podałeś żadnych dowodów na to), następnie powinieneś zamknąć zarządzanie pamięcią w klasie za pomocą konstruktorów w celu przydzielenia pamięci i dekonstruktorów w celu dealokacji pamięci.

Twoja odpowiedź, że twój sposób robienia rzeczy jest znacznie łatwiejszy, nie jest tak naprawdę prawdziwa na dłuższą metę. Po pierwsze, gdy poczujesz się dobrze w C++, tworzenie takich konstruktorów będzie drugą naturą. Osobiście uważam, że używanie Konstruktorów jest łatwiejsze niż używanie kodu oczyszczającego, ponieważ nie mam potrzeby zwróć szczególną uwagę, aby upewnić się, że jestem dealokacji prawidłowo. Zamiast tego mogę po prostu pozwolić obiektowi opuścić zakres, A Język obsługuje go za mnie. Ponadto utrzymanie ich jest znacznie łatwiejsze niż utrzymanie sekcji czyszczenia i znacznie mniej podatne na problemy.

W skrócie, goto może być dobrym wyborem w niektórych sytuacjach, ale nie w tej. Tutaj to tylko krótkotrwałe lenistwo.

 22
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-10 08:09:00

Twój kod jest wyjątkowo nie-idiomatyczny i nigdy nie powinieneś go pisać. W zasadzie emulujesz C w C++. Ale inni zwrócili na to uwagę i wskazali na RAII jako alternatywę.

Jednak Twój kod nie będzie działał tak jak się spodziewasz, ponieważ to:

p = new int;
if(p==NULL) { … }

Nie będzie nigdy Oceniać do true (chyba, że przeciążyłeś operator new w dziwny sposób). Jeśli operator new nie jest w stanie przydzielić wystarczającej ilości pamięci, rzuca wyjątek, nigdy, ever zwraca 0, przynajmniej nie z tym zestawem parametrów; istnieje specjalne przeciążenie, które pobiera instancję typu {[6] } i które rzeczywiście zwraca 0 zamiast rzucać wyjątek. Jednak ta wersja jest rzadko używana w normalnym kodzie. Niektóre niskopoziomowe kody lub aplikacje urządzeń wbudowanych mogą z nich korzystać w sytuacjach, w których radzenie sobie z wyjątkami jest zbyt kosztowne.

Coś podobnego jest prawdą dla Twojego delete bloku, jak powiedział Harald: if (p) jest zbędne przed delete p.

Dodatkowo, nie jestem pewien, czy twój przykład został wybrany celowo, ponieważ ten kod można przepisać w następujący sposób:

bool foo() // prefer native types to BOOL, if possible
{
    bool ret = false;
    int i;
    // Lots of code.
    return ret;
}
 20
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:14:29
 16
Author: Marc Charbonneau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 20:53:10

Ogólnie rzecz biorąc, nie ma nic złego w twoim podejściu, pod warunkiem, że masz tylko jedną etykietę i że Goto zawsze idzie do przodu. Na przykład ten kod:

int foo()
{
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        delete pWhatEver;
        return 1;
    }
    else
    {
        delete pWhatEver;
        return 5;
    }
}

I ten kod:

int foo()
{
    int ret;
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        ret = 1;
        goto exit;
    }
    else
    {
        ret = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}
Nie różnią się od siebie aż tak bardzo. Jeśli możesz zaakceptować jedną, powinieneś być w stanie zaakceptować drugą.

Jednak w wielu przypadkach wzorzec RAII (pozyskiwanie zasobów jest inicjalizacją) może sprawić, że kod będzie dużo czystszy i bardziej / align = "left" / Na przykład ten kod:

int foo()
{
    Auto<int> pWhatEver = ...;

    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

Jest krótszy, łatwiejszy do odczytania i łatwiejszy w utrzymaniu niż oba poprzednie przykłady.

Więc, polecam użycie podejścia RAII, jeśli możesz.
 11
Author: Scott Wisniewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:03:41

Twój przykład nie jest bezpieczny.

Jeśli używasz goto do czyszczenia kodu, jeśli wyjątek stanie się przed kodem czyszczenia, zostanie on całkowicie pominięty. Jeśli twierdzisz, że nie używasz WYJĄTKÓW, to jesteś w błędzie, ponieważ new wyrzuci bad_alloc, gdy nie ma wystarczającej ilości pamięci.

Również w tym momencie (gdy zostanie wyrzucony bad_alloc), twój stos zostanie rozwinięty, pomijając cały kod czyszczenia w każdej funkcji po drodze do stosu wywołania, więc nie wyczyścisz podaj swój kod.

Musisz poszukać informacji na temat inteligentnych wskaźników. W powyższej sytuacji możesz po prostu użyć std::auto_ptr<>.

Zauważ również, że w kodzie C++ nie ma potrzeby sprawdzania, czy wskaźnik jest NULL (zwykle dlatego, że nigdy nie masz nieprzetworzonych wskaźników), ale dlatego, że new nie zwróci NULL (rzuca).

Również w C++ w przeciwieństwie do (C) często widzi się wczesne zwroty w kodzie. Dzieje się tak dlatego, że RAII wykona czyszczenie automatycznie, podczas gdy w kodzie C musisz upewnij się, że dodajesz specjalny kod oczyszczania na końcu funkcji(trochę jak Twój kod).

 8
Author: Loki Astari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:11:58

Myślę, że inne odpowiedzi (i ich komentarze) obejmowały wszystkie ważne punkty, ale tutaj jest jedna rzecz, która nie została jeszcze wykonana poprawnie:

Jak powinien wyglądać Twój kod zamiast:

bool foo() //lowercase bool is a built-in C++ type. Use it if you're writing C++.
{
  try {
    std::unique_ptr<int> p(new int);
    // lots of code, and just return true or false directly when you're done
  }
  catch (std::bad_alloc){ // new throws an exception on OOM, it doesn't return NULL
    cout<<" OOM \n";
    return false;
  }
}

Cóż, jest krótszy i o ile widzę, bardziej poprawny (obsługuje sprawę OOM poprawnie), a co najważniejsze, nie musiałem pisać żadnego kodu czyszczącego ani robić niczego specjalnego, aby "upewnić się, że moja wartość zwrotu jest zainicjalizowana".

Jeden problem z Twoim kodem tylko naprawdę zauważyłem kiedy to napisałem, to " jaka do cholery jest wartość bretvala w tym momencie?". Nie wiem, bo zostało ogłoszone waaaaay powyżej i zostało ostatnio przypisane do kiedy? W pewnym momencie powyżej tego. Muszę przeczytać całą funkcję, aby upewnić się, że rozumiem, co zostanie zwrócone.

I jak mam przekonać siebie, że pamięć zostaje uwolniona?

Skąd mam wiedzieć że nigdy nie zapominamy przeskoczyć do etykiety sprzątania? Muszę pracować wstecz od etykiety cleanup, znajdując każdy goto wskazuje na to, i co ważniejsze, znaleźć te, których nie ma. Muszę prześledzić wszystkie ścieżki funkcji, aby mieć pewność, że funkcja zostanie prawidłowo wyczyszczona. To brzmi jak kod spaghetti.

Bardzo delikatny Kod, ponieważ w każdym czasie gdy zasób musi być oczyszczony musisz pamiętać aby zduplikować swój kod oczyszczający. Dlaczego nie napisać go raz, w typie, który trzeba posprzątać? A potem polegać na tym, że jest wykonywane automatycznie, za każdym razem, gdy tego potrzebujemy?

 8
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-04 23:41:05

Jak używane w jądrze Linuksa, goto używane do czyszczenia działa dobrze, gdy pojedyncza funkcja musi wykonać 2 lub więcej kroków, które mogą wymagać cofnięcia. Kroki nie muszą być alokacja pamięci. Może to być zmiana konfiguracji na fragment kodu lub w rejestrze chipsetu We / Wy. Goto powinny być potrzebne tylko w niewielkiej liczbie przypadków, ale często, gdy są używane poprawnie, mogą być najlepszym rozwiązaniem {4]}. Nie są źli. Są narzędziem.

Zamiast...

do_step1;
if (failed)
{
  undo_step1;
  return failure;
}

do_step2;
if (failed)
{
  undo_step2;
  undo_step1;
  return failure;
}

do_step3;
if (failed)
{
  undo_step3;
  undo_step2;
  undo_step1;
  return failure;
}

return success;

Możesz zrobić to samo z takimi wypowiedziami goto:

do_step1;
if (failed) goto unwind_step1;

do_step2;
if (failed) goto unwind_step2;

do_step3;
if (failed) goto unwind_step3;

return success;

unwind_step3:
  undo_step3;

unwind_step2:
  undo_step2;

unwind_step1:
  undo_step1;

return failure;

Powinno być jasne, że biorąc pod uwagę te dwa przykłady, jeden jest lepszy od drugiego. Co do tłumu RAII... Nie ma nic złego w tym podejściu, o ile mogą zagwarantować, że odwijanie zawsze będzie miało miejsce w dokładnie odwrotnej kolejności: 3, 2, 1. I wreszcie, niektórzy ludzie nie używają WYJĄTKÓW w swoim kodzie i instruują Kompilatory, aby je wyłączyć. Dlatego nie wszystkie kody muszą być bezpieczne dla WYJĄTKÓW.

 6
Author: Harvey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 19:10:22

Powinieneś przeczytać podsumowanie tego wątku z list dyskusyjnych jądra Linuksa (zwracając szczególną uwagę na odpowiedzi Linusa Torvaldsa) przed utworzeniem Polityki Dla goto:

Http://kerneltrap.org/node/553/2131

 6
Author: too much php,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:16:02

W ciągu ośmiu lat programowania często używałem goto, większość z tego było w pierwszym roku, kiedy używałem wersji GW-BASIC i książki z 1980 roku, która nie wyjaśniała, że goto powinno być używane tylko w niektórych przypadkach. Jedyny raz używałem goto w C++ , Kiedy miałem kod podobny do poniższego i nie jestem pewien, czy był lepszy sposób.

for (int i=0; i<10; i++) {
    for (int j=0; j<10; j++)
    {
        if (somecondition==true)
        {
            goto finish;
        }
        //Some code
    }
    //Some code
}
finish:

Jedyną sytuacją, jaką znam, gdzie goto jest nadal intensywnie używany, jest język montażu mainframe, a programiści I pamiętaj, aby udokumentować, gdzie kod skacze i dlaczego.

 6
Author: Jared,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:35:38

Ogólnie rzecz biorąc, powinieneś zaprojektować swoje programy, aby ograniczyć zapotrzebowanie na gotos. Użyj technik OO do "czyszczenia" zwracanych wartości. Istnieją sposoby, aby to zrobić, które nie wymagają użycia gotos lub komplikowania kodu. Istnieją przypadki, w których Goto są bardzo przydatne (na przykład głęboko zagnieżdżone zakresy), ale jeśli to możliwe, należy ich unikać.

 5
Author: Marcin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:08:00

Minusy GOTO są dość dobrze omówione. Dodam tylko, że 1) Czasami trzeba z nich korzystać i trzeba wiedzieć, jak zminimalizować problemy, oraz 2) niektóre przyjęte techniki programowania są GOTO-in-disguise, więc bądź ostrożny.

1) gdy musisz użyć GOTO, np. w ASM lub w .pliki bat, myśl jak kompilator. Jeśli chcesz kodować

 if (some_test){
  ... the body ...
}

Rób to, co robi kompilator. Wygeneruj Etykietę, której celem jest przeskoczenie nad ciałem, a nie robienie tego, co następuje. tj.

 if (not some_test) GOTO label_at_end_of_body
  ... the body ...
label_at_end_of_body:

Nie

 if (not some_test) GOTO the_label_named_for_whatever_gets_done_next
  ... the body ...

the_label_named_for_whatever_gets_done_next:

W otherwords, celem etykiety nie jest zrobienie czegoś, ale pominięcie czegoś.

2) to, co nazywam GOTO-in-disguise, to wszystko, co można przekształcić w kod GOTO+LABELS poprzez zdefiniowanie kilku makr. Przykładem może być technika implementacji automatów skończonych poprzez posiadanie zmiennej stanu oraz instrukcji while-switch.

 while (not_done){
    switch(state){
        case S1:
            ... do stuff 1 ...
            state = S2;
            break;
        case S2:
            ... do stuff 2 ...
            state = S1;
            break;
        .........
    }
}

Może przekształcić się w:

 while (not_done){
    switch(state){
        LABEL(S1):
            ... do stuff 1 ...
            GOTO(S2);
        LABEL(S2):
            ... do stuff 2 ...
            GOTO(S1);
        .........
    }
}

Po prostu definiując parę makra. Prawie każdy FSA może zostać przekształcony w ustrukturyzowany kod goto-less. Wolę trzymać się z dala od kodu GOTO-in-disguise, ponieważ może on wchodzić w te same problemy z kodem spaghetti, co nieskrywane Goto.

Dodany: dla pocieszenia: myślę, że jedną z cech dobrego programisty jest rozpoznawanie, kiedy nie obowiązują wspólne zasady.

 5
Author: Mike Dunlavey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 20:15:23

Goto zapewnia lepsze nie powtarzaj się (suchy), gdy "logika końca ogona" jest wspólna dla niektórych-ale-nie-wszystkich-przypadków. Szczególnie w ramach instrukcji "switch" często używam goto, gdy niektóre gałęzie przełącznika mają wspólną końcówkę.

switch(){
   case a:  ... goto L_abTail;
   case b: ... goto L_abTail;
L_abTail: <commmon stuff>
    break://end of case b
case c:
.....
}//switch

Prawdopodobnie zauważyłeś, że wprowadzenie dodatkowych nawiasów klamrowych wystarczy, aby zaspokoić kompilator, gdy potrzebujesz takiego łączenia końcówek w środku procedury. Innymi słowy, nie musisz deklarować wszystkiego na górze; to rzeczywiście gorsza czytelność.

...
   goto L_skipMiddle;
{
    int declInMiddleVar = 0;
    ....
}
L_skipMiddle: ;

Z późniejszymi wersjami Visual Studio wykrywając użycie niezaliczonych zmiennych, zawsze inicjuję większość zmiennych, mimo że myślę, że mogą być przypisane we wszystkich gałęziach - łatwo jest zakodować instrukcję "śledzenia", która refs zmiennej, która nigdy nie została przypisana, ponieważ twój umysł nie myśli o instrukcji śledzenia jako" prawdziwy kod", ale oczywiście Visual Studio nadal wykryje błąd.

Poza tym nie powtórz sobie, przypisywanie nazw etykiet do takiej logiki ogonowej nawet wydaje się pomóc mojemu umysłowi utrzymać rzeczy prosto, wybierając ładne nazwy etykiet. Bez znaczącej etykiety Twoje komentarze mogą skończyć się powiedzeniem tego samego.

Oczywiście, jeśli faktycznie przydzielasz zasoby, to jeśli auto-PST nie pasuje, naprawdę musisz użyć try-catch, ale tail-end-merge-don ' t-repeat-yourself zdarza się dość często, gdy wyjątek-bezpieczeństwo nie jest problemem.

W podsumowaniu, podczas gdy goto może być użyte do kodowania struktury podobne do spaghetti, w przypadku sekwencji ogonowej, która jest wspólna dla niektórych-ale-nie-wszystkich-przypadków, goto poprawia czytelność kodu, a nawet konserwowalność, jeśli w przeciwnym razie będziesz kopiował / wklejał rzeczy, aby znacznie później ktoś mógł zaktualizować jedno-a-nie-drugie. Jest to więc kolejny przypadek, w którym bycie fanatykiem dogmatu może przynieść odwrotny skutek.

 5
Author: pngaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:21:30

Używanie goto, aby przejść do sekcji czyszczenia, spowoduje wiele problemów.

Po pierwsze, sekcje sprzątania są podatne na problemy. Mają niską spójność (nie ma prawdziwej roli, którą można opisać w kategoriach tego, co program próbuje zrobić), wysoką sprzężenie (poprawność zależy bardzo mocno od innych sekcji kodu) i wcale nie są bezpieczne dla WYJĄTKÓW. Spróbuj użyć destruktorów do czyszczenia. Na przykład, jeśli int *p zostanie zmieniona na auto_ptr<int> p, to co wskazuje p będzie automatycznie zwolniony.

Po drugie, jak zauważyłeś, zmusi cię to do zadeklarowania zmiennych na długo przed użyciem, co utrudni zrozumienie kodu.

Po trzecie, podczas gdy proponujesz dość zdyscyplinowane użycie goto, pojawi się pokusa, aby używać ich w luźniejszy sposób, a wtedy kod stanie się trudny do zrozumienia. Jest bardzo niewiele sytuacji, w których goto jest właściwe. W większości przypadków, kiedy kusi się ich używać, jest to sygnał, że źle robisz.
 4
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 20:48:44

Ponieważ jest to klasyczny temat, odpowiem Go-to oświadczenie Dijkstry uznane za szkodliwe (pierwotnie opublikowane w ACM).

 4
Author: mstrobl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:04:42

Całym celem idiomu każda funkcja ma jeden punkt wyjścia w C było umieszczenie wszystkich rzeczy czyszczących w jednym miejscu. Jeśli używasz destruktorów C++ do czyszczenia, nie jest to już konieczne-oczyszczanie zostanie wykonane niezależnie od liczby punktów wyjścia funkcji. Tak więc w poprawnie zaprojektowanym kodzie C++ nie ma już takiej potrzeby.

 3
Author: Head Geek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:01:31

Wiele osób szaleje z gotos są źli; nie są. To powiedziawszy, nigdy nie będziesz go potrzebował; zawsze jest lepszy sposób.

Kiedy znajduję się" potrzebujący " goto do tego typu rzeczy, prawie zawsze stwierdzam, że mój kod jest zbyt złożony i można go łatwo podzielić na kilka wywołań metod, które są łatwiejsze do odczytania i radzenia sobie z nimi. Twój kod może zrobić coś takiego:

// Setup
if(
     methodA() &&
     methodB() &&
     methodC()
 )
 // Cleanup

Nie to, że jest to idealne, ale jest to o wiele łatwiejsze do naśladowania, ponieważ wszystkie Twoje metody zostanie nazwany, aby wyraźnie wskazać, jaki może być problem.

Czytanie komentarzy powinno jednak wskazywać, że Wasz zespół ma bardziej palące problemy niż obsługa goto.

 3
Author: Bill K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:13:48

Kod, który nam dajesz, to (prawie) kod C napisany wewnątrz pliku C++. Rodzaj czyszczenia pamięci, którego używasz, byłby OK w programie C, który nie używa kodu/bibliotek C++.

W C++ Twój kod jest po prostu niebezpieczny i zawodny. W C++ zarządzanie, o które prosisz, odbywa się inaczej. Użyj konstruktorów/destruktorów. Użyj inteligentnych wskaźników. Użyj stosu. Jednym słowem, użyj RAII .

Twój kod może (tj. w C++ powinien) być napisany jako:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<int> p = new int;

   // Lot of code...

   return bRetVal ;
}

(zauważ, że new - ing INT jest nieco głupi w prawdziwym kodzie, ale można zastąpić int dowolnym rodzajem obiektu, i wtedy, to ma więcej sensu). Wyobraźmy sobie, że mamy obiekt typu T (T może być int, jakąś klasą C++, itp.). Wtedy kod staje się:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<T> p = new T;

   // Lot of code...

   return bRetVal ;
}

Lub nawet lepiej, używając stosu:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   T p ;

   // Lot of code...

   return bRetVal;
}

W każdym razie, każdy z powyższych przykładów jest łatwiejszy do odczytania i bezpieczniejszy niż twój przykład.

RAII ma wiele aspektów (tj. używanie inteligentnych wskaźników, stos, używanie wektorów zamiast tablice o zmiennej długości itp.), ale w sumie chodzi o pisanie jak najmniejszej ilości kodu, pozwalając kompilatorowi posprzątać rzeczy w odpowiednim momencie.

 2
Author: paercebal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:23:48

Jedyne dwa powody, dla których używam goto w moim kodzie C++ to:

  • łamanie poziomu 2+ zagnieżdżone pętle
  • Skomplikowane przepływy takie jak ten (komentarz w moim programie):

    /* Analysis algorithm:
    
      1.  if classData [exporter] [classDef with name 'className'] exists, return it,
          else
      2.    if project/target_codename/temp/classmeta/className.xml exist, parse it and go back to 1 as it will succeed.
      3.    if that file don't exists, generate it via haxe -xml, and go back to 1 as it will succeed.
    
    */
    

Dla czytelności kodu, po tym komentarzu zdefiniowałem Etykietę step1 i użyłem jej w Kroku 2 i 3. Właściwie, w plikach źródłowych 60+, tylko ta sytuacja i jeden 4-poziomowy zagnieżdżony są miejscami, w których użyłem goto. Tylko dwa miejsca.

 2
Author: Петър Петров,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:41:17

Wszystkie powyższe informacje są poprawne, możesz również sprawdzić, czy możesz być w stanie zmniejszyć złożoność kodu i złagodzić potrzebę goto poprzez zmniejszenie ilości kodu, który znajduje się w sekcji oznaczonej jako "dużo kodu" w twoim przykładzie. Additionaly delete 0 is a valid C++ statement

 1
Author: Harald Scheirich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 20:55:12

Używanie etykiet GOTO w C++ jest złym sposobem na programowanie, możesz zmniejszyć potrzebę wykonując oo programowanie (deconstructors!) i stara się zachować procedury jak najmniejsze .

Twój przykład wygląda trochę dziwnie, nie ma potrzeby usuwania wskaźnika NULL . A obecnie wyjątek jest wyrzucany, gdy wskaźnik nie może zostać przydzielony.

Twój zabieg można napisać tak:

bool foo()
{
    bool bRetVal = false;
    int p = 0;

    // Calls to various methods that do algorithms on the p integer
    // and give a return value back to this procedure.

    return bRetVal;
}

Powinieneś umieścić try catch block w głównej obsłudze programu out of memory problems, który informuje użytkownika o braku pamięci, co jest bardzo rzadkie ... (Czy sam OS też o tym nie informuje?)

Zauważ również, że nie zawsze istnieje potrzeba używania wskaźnika , są one użyteczne tylko dla dynamicznych rzeczy . (Tworzenie jednej rzeczy wewnątrz metody nie zależnej od danych wejściowych z dowolnego miejsca nie jest tak naprawdę dynamiczne)

 1
Author: Tom Wijsman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:06:24

Nie powiem, że goto zawsze jest źle, ale twoje użycie tego z pewnością jest. Ten rodzaj "Cleanup sections" był dość powszechny na początku lat 90-tych, ale używanie go do nowego kodu jest czystym złem.

 1
Author: Nemanja Trifunovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:41:09

Najprostszym sposobem na uniknięcie tego, co tutaj robisz, jest umieszczenie tego wszystkiego w jakiejś prostej strukturze i utworzenie jego instancji. Na przykład zamiast:

void MyClass::myFunction()
{
   A* a = new A;
   B* b = new B;
   C* c = new C;
   StartSomeBackgroundTask();
   MaybeBeginAnUndoBlockToo();

   if ( ... )
   {
     goto Exit;
   }

   if ( ... ) { .. }
   else
   {
      ... // what happens if this throws an exception??? too bad...
      goto Exit;
   }

Exit:
  delete a;
  delete b;
  delete c;
  StopMyBackgroundTask();
  EndMyUndoBlock();
}

Powinieneś raczej zrobić to sprzątanie w jakiś sposób:

struct MyFunctionResourceGuard
{
  MyFunctionResourceGuard( MyClass& owner ) 
  : m_owner( owner )
  , _a( new A )
  , _b( new B )
  , _c( new C )
  {
      m_owner.StartSomeBackgroundTask();
      m_owner.MaybeBeginAnUndoBlockToo();
  }

  ~MyFunctionResourceGuard()
  {
     m_owner.StopMyBackgroundTask();
     m_owner.EndMyUndoBlock();
  }

  std::auto_ptr<A> _a;
  std::auto_ptr<B> _b;
  std::auto_ptr<C> _c;

};

void MyClass::myFunction()
{
   MyFunctionResourceGuard guard( *this );

   if ( ... )
   {
     return;
   }

   if ( ... ) { .. }
   else
   {
      ...
   }
}
 1
Author: Michel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:46:59

Kilka lat temu wymyśliłem pseudo-idiom, który unika goto i jest niejasno podobny do obsługi wyjątków w C. prawdopodobnie został już wymyślony przez kogoś innego, więc chyba "odkryłem go niezależnie":)

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p=NULL;

   do
   {
       p = new int;
       if(p==NULL)
       {
          cout<<" OOM \n";
          break;
       }

       // Lot of code...

       bRetVal = TRUE;

    } while (false);

   if(p)
   {
     delete p;
     p= NULL;
   }

   return bRetVal;
}
 1
Author: ggambett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-20 14:56:56

Myślę, że używanie goto dla kodu wyjścia jest złe, ponieważ istnieje wiele innych rozwiązań z niskim obciążeniem, takich jak posiadanie funkcji exit i zwracanie wartości funkcji exit w razie potrzeby. Zazwyczaj jednak w funkcjach członkowskich nie powinno to być potrzebne, w przeciwnym razie może to wskazywać na to, że dzieje się zbyt wiele nadcięcia kodu.

Zazwyczaj jedynym wyjątkiem, jaki robię od reguły" no goto " podczas programowania jest wyłamywanie się z zagnieżdżonych pętli na określony poziom, co tylko natknąłem się na potrzebę zrobienia podczas pracy nad programowaniem matematycznym.

Na przykład:

for(int i_index = start_index; i_index >= 0; --i_index)
{
    for(int j_index = start_index; j_index >=0; --j_index)
        for(int k_index = start_index; k_index >= 0; --k_index)
            if(my_condition)
                goto BREAK_NESTED_LOOP_j_index;
BREAK_NESTED_LOOP_j_index:;
}
 1
Author: Hazok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-11 22:30:33

Ten kod ma kilka problemów, z których większość została już wskazana, na przykład:

  • Funkcja jest zbyt długa; refaktoryzacja kodu w osobne funkcje może pomóc.

  • Używanie wskaźników, gdy normalne instancje będą prawdopodobnie działać dobrze.

  • Nie wykorzystując typów STL takich jak auto_ptr

  • Nieprawidłowe sprawdzanie błędów i nie wyłapywanie WYJĄTKÓW. (Argumentowałbym, że sprawdzanie OOM jest bezsensowne na zdecydowanej większości platform, ponieważ jeśli zabraknie Ci pamięci, masz większe problemy niż Twoje oprogramowanie może naprawić, chyba że piszesz sam OS)

Nigdy nie potrzebowałem goto i zawsze uważałem, że używanie goto jest objawem większego zestawu problemów. Twoja sprawa nie jest wyjątkiem.

 1
Author: metao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 06:15:41

Użycie "GOTO" zmieni "logikę" programu i sposób, w jaki go wprowadzasz lub jak wyobrażasz sobie jego działanie.

Unikanie GOTO-komend zawsze działało dla mnie, więc Zgadnij, kiedy myślisz, że możesz go potrzebować, wszystko, czego potrzebujesz, to przeprojektowanie.

Jednak, jeśli spojrzymy na to na poziomie Assmebly, jusing "jump" jest jak używanie GOTO i to jest używane przez cały czas, ale w Assembly możesz wyczyścić to, co wiesz, że masz na stosie i innych rejestrach, zanim przejdziesz on

Tak więc, podczas korzystania z GOTO, upewniłbym się, że oprogramowanie "pojawi się" tak, jak współkoderzy będą wchodzić w interakcję, GOTO będzie miało " zły " wpływ na oprogramowanie imho.

Więc jest to bardziej wyjaśnienie dlaczego nie używać GOTO, a nie rozwiązanie do wymiany, ponieważ to zależy bardzo od tego, jak Wszystko inne jest zbudowane.

 0
Author: Filip Ekberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 21:02:30

Mogłem coś przeoczyć: przeskakujesz do wyjścia etykiety, jeśli P jest null, następnie Testuj, aby zobaczyć, czy nie jest null (co nie jest), aby zobaczyć, czy musisz go usunąć(co nie jest konieczne, ponieważ nigdy nie zostało przydzielone w pierwszej kolejności).

If/goto nie będzie i nie musi usuwać P. zastąpienie goto zwrotem false miałoby taki sam efekt (a następnie można usunąć etykietę wyjścia).

Jedyne miejsca, które znam, gdzie Goto są przydatne, są zakopane głęboko w paskudnych parserach (lub analizatorów leksykalnych), a w maszynach państwowych (zakopanych w masie makr CPP). W tych dwóch przypadkach zostały one wykorzystane do bardzo pokręconej logiki prostsze, ale to jest bardzo rzadkie.

Funkcje (a wywołuje A'), Try / Catches i setjmp / longjmps są ładniejszymi sposobami na uniknięcie trudnego problemu ze składnią.

Paweł.

 0
Author: Paul W Homer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 23:19:31

Ignorując fakt, że new nigdy nie zwróci NULL, weź swój kod:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p=NULL;

     p = new int;

     if(p==NULL)
     {
        cout<<" OOM \n";
        goto Exit;
     }

     // Lot of code...

  Exit:
     if(p)
     {
        delete p;
        p= NULL;
     }

     return bRetVal;
  }

I napisz to tak:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p = new int;

     if (p!=NULL)
     {
        // Lot of code...

        delete p;
     }
     else
     {
        cout<<" OOM \n";
     }

     return bRetVal;
  }
 0
Author: jussij,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 23:38:07