Ciekawe odpowiedzi: Chociaż zgadzam się z nimi wszystkimi (na razie), istnieją możliwe konotacje tego pytania, które do tej pory są całkowicie pomijane.
Jeśli powyższy prosty przykład zostanie rozszerzony o alokację zasobów, a następnie sprawdzanie błędów z potencjalnym wynikającym z tego zwolnieniem zasobów, obraz może się zmienić.
Rozważ naiwne podejście, które mogą przyjąć początkujący:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Powyższe reprezentowałoby skrajną wersję stylu przedwczesnego powrotu. Zwróć uwagę, jak kod staje się bardzo powtarzalny i niemożliwy do utrzymania w czasie, gdy rośnie jego złożoność. W dzisiejszych czasach ludzie mogą używać obsługi wyjątków, aby je złapać.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip zasugerował, po obejrzeniu poniższego przykładu goto, aby użyć bezłamkowego przełącznika / obudowy wewnątrz bloku catch powyżej. Można by się przełączyć (typeof (e)), a następnie upaść przez free_resourcex()
wywołania, ale nie jest to trywialne i wymaga rozważenia projektowego . I pamiętaj, że przełącznik / obudowa bez przerw jest dokładnie taki sam, jak goto z połączonymi łańcuchami etykietami poniżej ...
Jak zauważył Mark B, w C ++ za dobry styl uważa się podążanie za zasadą pozyskiwania zasobów to inicjalizacja , w skrócie RAII . Istotą koncepcji jest użycie instancji obiektu do pozyskiwania zasobów. Zasoby są następnie automatycznie zwalniane, gdy tylko obiekty wyjdą poza zasięg i zostaną wywołane ich destruktory. W przypadku współzależnych zasobów należy zwrócić szczególną uwagę, aby zapewnić prawidłową kolejność zwalniania alokacji i zaprojektować takie typy obiektów, aby wymagane dane były dostępne dla wszystkich destruktorów.
Lub w dni przed wyjątkiem może to zrobić:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Ale ten nadmiernie uproszczony przykład ma kilka wad: Można go używać tylko wtedy, gdy przydzielone zasoby nie są od siebie zależne (np. Nie można go użyć do alokacji pamięci, a następnie otwarcia uchwytu pliku, a następnie wczytania danych z uchwytu do pamięci ) i nie dostarcza indywidualnych, rozróżnialnych kodów błędów jako wartości zwracanych.
Aby kod był szybki (!), Zwarty, czytelny i rozszerzalny, Linus Torvalds narzucił inny styl kodu jądra, który zajmuje się zasobami, nawet używając niesławnego goto w sposób, który ma absolutnie sens :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
Istotą dyskusji na listach dyskusyjnych jądra jest to, że większość funkcji językowych, które są „preferowane” w stosunku do instrukcji goto, to niejawne goto, takie jak ogromne, podobne do drzewa if / else, programy obsługi wyjątków, instrukcje pętli / przerwania / kontynuacji itp. .A goto w powyższym przykładzie są uważane za w porządku, ponieważ skaczą tylko na niewielką odległość, mają wyraźne etykiety i uwalniają kod z innych bałaganów do śledzenia warunków błędów. To pytanie zostało również omówione tutaj na temat przepełnienia stosu .
Jednak to, czego brakuje w ostatnim przykładzie, to dobry sposób na zwrócenie kodu błędu. Myślałem o dodaniu result_code++
po każdym free_resource_x()
wywołaniu i zwróceniu tego kodu, ale to zniwelowało niektóre przyrosty szybkości wynikające z powyższego stylu kodowania. I trudno jest zwrócić 0 w przypadku sukcesu. Może po prostu nie mam wyobraźni ;-)
Więc tak, myślę, że jest duża różnica w kwestii kodowania przedwczesnych zwrotów, czy nie. Ale myślę również, że jest to widoczne tylko w bardziej skomplikowanym kodzie, którego restrukturyzacja i optymalizacja pod kątem kompilatora jest trudniejsza lub niemożliwa. Co zwykle ma miejsce, gdy w grę wchodzi alokacja zasobów.