Niepotrzebne nawiasy klamrowe w C ++?

187

Dokonując dzisiaj przeglądu kodu dla kolegi, zauważyłem osobliwą rzecz. Swój nowy kod otoczył kręconymi nawiasami klamrowymi:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Jaki jest z tego wynik? Jaki może być powód tego? Skąd pochodzi ten nawyk?

Edytować:

Na podstawie danych wejściowych i niektórych pytań poniżej czuję, że muszę dodać trochę do pytania, mimo że już zaznaczyłem odpowiedź.

Środowisko to urządzenia osadzone. Istnieje wiele starszego kodu C zawiniętego w odzież C ++. Istnieje wiele programistów C ++ w wersji C.

W tej części kodu nie ma sekcji krytycznych. Widziałem to tylko w tej części kodu. Nie są wykonywane żadne główne przydziały pamięci, tylko niektóre flagi, które są ustawione, a niektóre drżenie.

Kod otoczony nawiasami klamrowymi przypomina:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

(Nie przejmuj się kodem, po prostu trzymaj się nawiasów klamrowych ...;)) Po nawiasach klamrowych jest trochę więcej kręcenia, sprawdzania stanu i podstawowej sygnalizacji.

Rozmawiałem z facetem, a jego motywacją było ograniczenie zakresu zmiennych, konfliktów nazw i innych, których tak naprawdę nie mogłem zrozumieć.

Z mojego POV wydaje się to dość dziwne i nie sądzę, że nawiasy klamrowe powinny znajdować się w naszym kodzie. We wszystkich odpowiedziach widziałem kilka dobrych przykładów, dlaczego można otoczyć kod nawiasami klamrowymi, ale nie powinieneś zamiast tego rozdzielić kodu na metody?

iikkoo
źródło
99
Jaka była odpowiedź twojego kolegi, kiedy zapytałeś go, dlaczego to zrobił?
Graham Borland,
20
Dość powszechne z wzorcem RAII. Krótki przegląd: c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin
9
Nienawidzę niepotrzebnych nawiasów klamrowych
jacknad
8
Czy były jakieś deklaracje w wewnętrznym bloku?
Keith Thompson
15
może po prostu chciał łatwo „złożyć” tę nową sekcję w swoim edytorze
wim

Odpowiedzi:

281

Czasami jest to przyjemne, ponieważ daje nowy zakres, w którym możesz bardziej „czysto” zadeklarować nowe (automatyczne) zmienne.

W C++tym być może nie jest to tak ważne, ponieważ możesz wprowadzać nowe zmienne w dowolnym miejscu, ale być może nawyk pochodzi z tego C, że nie możesz tego robić aż do C99. :)

Ponieważ C++ma destruktory, przydatne może być również automatyczne uwalnianie zasobów (plików, muteksów itp.) Po wyjściu z zakresu, co może uczynić wszystko czystszym. Oznacza to, że możesz trzymać jakiś udostępniony zasób przez krótszy czas niż w przypadku, gdybyś złapał go na początku metody.

rozwijać
źródło
37
+1 za wyraźne wskazanie nowych zmiennych i starych nawyków
arne
46
+1 za użycie zakresu bloków, aby zwolnić zasoby tak szybko, jak to możliwe
Leo
9
Łatwo jest także „if (0)” bloku.
vrdhn
Moi recenzenci kodu często mówią mi, że piszę zbyt krótkie funkcje / metody. Aby zapewnić im radość i zadowolenie (tj. Oddzielić niepowiązane obawy, zmienną lokalizację itp.), Używam właśnie tej techniki opracowanej przez @unwind.
ossandcad
21
@ossandcad, mówią ci, że twoje metody są „za krótkie”? To bardzo trudne do zrobienia. 90% programistów (w tym prawdopodobnie mnie) ma odwrotny problem.
Ben Lee
169

Jednym z możliwych celów jest kontrola zakresu zmiennego . A ponieważ zmienne z automatycznym zapamiętywaniem są niszczone, gdy wykraczają poza zakres, może to również umożliwić wywołanie destruktora wcześniej niż w innym przypadku.

ruakh
źródło
14
Oczywiście, naprawdę ten blok powinien zostać przekształcony w osobną funkcję.
BlueRaja - Danny Pflughoeft
8
Uwaga historyczna: Jest to technika z wczesnego języka C, która pozwoliła na tworzenie lokalnych zmiennych tymczasowych.
Thomas Matthews
12
Muszę powiedzieć - chociaż jestem zadowolony z mojej odpowiedzi, to naprawdę nie jest najlepsza odpowiedź tutaj; lepsze odpowiedzi wyraźnie wspominają RAII, ponieważ jest to główny powód, dla którego chcesz wywoływać destruktor w określonym punkcie. Wydaje się to być przypadkiem „najszybszej broni na Zachodzie”: opublikowałem wystarczająco szybko, aby uzyskać wystarczającą liczbę wczesnych głosów, aby zyskać „impet”, aby szybciej uzyskać głosy, niż niektóre lepsze odpowiedzi. Nie narzekam! :-)
ruakh
7
@ BlueRaja-DannyPflughoeft Upraszczasz. „Umieść w osobnej funkcji” nie jest rozwiązaniem każdego problemu z kodem. Kod w jednym z tych bloków może być ściśle powiązany z otaczającym kodem, dotykając kilku jego zmiennych. Korzystanie z funkcji C, które wymaga operacji wskaźnika. Co więcej, nie każdy fragment kodu jest (lub powinien być) wielokrotnego użytku, a czasem kod może nawet nie mieć sensu. Czasami blokuję moje forwypowiedzi, aby stworzyć krótkotrwały int i;w C89. Z pewnością nie sugerujesz, że każdy forpowinien mieć osobną funkcję?
Anders Sjöqvist,
101

Dodatkowe nawiasy klamrowe służą do definiowania zakresu zmiennej zadeklarowanej w nawiasach klamrowych. Odbywa się to tak, że destruktor zostanie wywołany, gdy zmienna wyjdzie poza zasięg. W destruktorze możesz uwolnić muteks (lub dowolny inny zasób), aby inni mogli go zdobyć.

W moim kodzie produkcyjnym napisałem coś takiego:

void f()
{
   //some code - MULTIPLE threads can execute this code at the same time

   {
       scoped_lock lock(mutex); //critical section starts here

       //critical section code
       //EXACTLY ONE thread can execute this code at a time

   } //mutex is automatically released here

  //other code  - MULTIPLE threads can execute this code at the same time
}

Jak widać, w ten sposób można użyć scoped_lock funkcji i jednocześnie zdefiniować jej zakres za pomocą dodatkowych nawiasów klamrowych. Zapewnia to, że nawet jeśli kod poza dodatkowymi nawiasami klamrowymi może być wykonywany przez wiele wątków jednocześnie, kod wewnątrz nawiasów będzie wykonywany przez dokładnie jeden wątek na raz.

Nawaz
źródło
1
Myślę, że czystsze jest po prostu mieć: scoped_lock lock (mutex) // kod sekcji krytycznej, a następnie lock.unlock ().
skwiercz
17
@szielenski: Co jeśli kod z sekcji krytycznej zgłasza wyjątek? Albo muteks zostanie zablokowany na zawsze, albo kod nie będzie tak przejrzysty, jak powiedziałeś.
Nawaz,
4
@Nawaz: Podejście @ szielenskiego nie pozostawia muteksu zamkniętego w przypadku wyjątków. Używa także scoped_locktego, który zostanie zniszczony na wyjątkach. Zazwyczaj wolę również wprowadzić nowy zakres blokady, ale w niektórych przypadkach unlockjest to bardzo przydatne. Np. Aby zadeklarować nową zmienną lokalną w sekcji krytycznej, a następnie użyć jej później. (Wiem, że się spóźniłem, ale dla kompletności ...)
Stephan,
51

Jak zauważyli inni, nowy blok wprowadza nowy zakres, umożliwiający napisanie odrobiny kodu z własnymi zmiennymi, które nie niszczą przestrzeni nazw otaczającego kodu i nie zużywają zasobów dłużej niż to konieczne.

Istnieje jednak inny dobry powód.

Jest to po prostu wyizolowanie bloku kodu, który osiąga określony (pod) cel. Rzadko zdarza się, że pojedyncze stwierdzenie osiąga oczekiwany przeze mnie efekt obliczeniowy; zwykle zajmuje to kilka. Umieszczenie ich w bloku (z komentarzem) pozwala mi powiedzieć czytelnikowi (często samemu w późniejszym terminie):

  • Ta część ma spójny cel koncepcyjny
  • Oto cały potrzebny kod
  • A oto komentarz na temat części.

na przykład

{  // update the moving average
   i= (i+1) mod ARRAYSIZE;
   sum = sum - A[i];
   A[i] = new_value;
   sum = sum + new_value;
   average = sum / ARRAYSIZE ;  
}

Możesz argumentować, że powinienem napisać funkcję, która to wszystko zrobi. Jeśli zrobię to tylko raz, napisanie funkcji dodaje tylko dodatkową składnię i parametry; nie ma sensu. Pomyśl o tym jak o bez parametrów, anonimowej funkcji.

Jeśli masz szczęście, twój edytor będzie miał funkcję składania / rozkładania, która pozwoli nawet ukryć blok.

Robię to cały czas. Z wielką przyjemnością znam granice kodu, który muszę sprawdzić, a jeszcze lepiej wiedzieć, że jeśli ten fragment nie jest tym, czego chcę, nie muszę patrzeć na żadną z linii.

Ira Baxter
źródło
23

Jednym z powodów może być to, że czas życia dowolnych zmiennych zadeklarowanych w nowym bloku nawiasów klamrowych jest ograniczony do tego bloku. Innym powodem, który przychodzi mi na myśl, jest możliwość zwijania kodu w ulubionym edytorze.

arne
źródło
17

Jest to to samo co blok if(lub whileetc ..), tylko bez if . Innymi słowy, wprowadzasz zakres bez wprowadzania struktury kontrolnej.

To „jawne określenie zakresu” jest zwykle przydatne w następujących przypadkach:

  1. Aby uniknąć konfliktów nazw.
  2. Do zakresu using.
  3. Aby kontrolować, kiedy wywoływane są destruktory.

Przykład 1:

{
    auto my_variable = ... ;
    // ...
}

// ...

{
    auto my_variable = ... ;
    // ...
}

Jeśli my_variablezdarza się, że jest to szczególnie dobra nazwa dla dwóch różnych zmiennych, które są używane w oderwaniu od siebie, jawne określenie zakresu pozwala uniknąć wymyślania nowej nazwy, aby uniknąć kolizji nazw.

Pozwala to również uniknąć przypadkowego użycia my_variablepoza zamierzonym zakresem.

Przykład 2:

namespace N1 { class A { }; }
namespace N2 { class A { }; }

void foo() {

    {
        using namespace N1;
        A a; // N1::A.
        // ...
    }

    {
        using namespace N2;
        A a; // N2::A.
        // ...
    }

}

Praktyczne sytuacje, w których jest to przydatne, są rzadkie i mogą wskazywać, że kod jest gotowy do refaktoryzacji, ale mechanizm jest tam, gdzie naprawdę go potrzebujesz.

Przykład 3:

{
    MyRaiiClass guard1 = ...;

    // ...

    {
        MyRaiiClass guard2 = ...;
        // ...
    } // ~MyRaiiClass for guard2 called.

    // ...

} // ~MyRaiiClass for guard1 called.

Może to być ważne dla RAII w przypadkach, gdy potrzeba uwolnienia zasobów nie „naturalnie” spada na granice funkcji lub struktur kontrolnych.

Branko Dimitrijevic
źródło
15

Jest to bardzo przydatne, gdy używa się zamków o zasięgu w połączeniu z krytycznymi sekcjami w programowaniu wielowątkowym. Blokada lunety zainicjowana w nawiasach klamrowych (zwykle pierwsze polecenie) wyłączy się z zakresu na końcu końca bloku, więc inne wątki będą mogły ponownie działać.

learnvst
źródło
14

Wszyscy inni już poprawnie uwzględniali możliwości określania zakresu, RAII itp., Ale ponieważ wspominasz o środowisku osadzonym, istnieje jeszcze jeden potencjalny powód:

Być może programista nie ufa przydziałowi rejestru tego kompilatora lub chce jawnie kontrolować rozmiar ramki stosu, ograniczając jednocześnie liczbę zmiennych automatycznych w zakresie.

Tutaj isInitprawdopodobnie będzie na stosie:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

Jeśli wyjmiesz nawiasy klamrowe, miejsce isInitw ramce stosu może zostać zarezerwowane nawet po potencjalnym ponownym użyciu: jeśli istnieje wiele zmiennych automatycznych o podobnie zlokalizowanym zakresie, a rozmiar stosu jest ograniczony, może to stanowić problem.

Podobnie, jeśli twoja zmienna jest przypisana do rejestru, wyjście poza zakres powinno dać mocną wskazówkę, że rejestr jest teraz dostępny do ponownego użycia. Będziesz musiał spojrzeć na asembler wygenerowany z nawiasami klamrowymi i bez nich, aby dowiedzieć się, czy to naprawdę robi różnicę (i profiluj go - lub obserwuj przepełnienie stosu - aby zobaczyć, czy ta różnica naprawdę ma znaczenie).

Bezużyteczny
źródło
+1 dobry punkt, chociaż jestem całkiem pewien, że nowoczesne kompilatory bez problemu interweniują. (IIRC - przynajmniej dla niewbudowanych kompilatorów - zignorowały słowo kluczowe „register” już w '99, ponieważ zawsze mogły wykonać lepszą pracę niż ty.)
Rup
11

Myślę, że inni już opisywali zakres, więc wspomnę, że niepotrzebne nawiasy klamrowe mogą również służyć celowi w procesie programowania. Załóżmy na przykład, że pracujesz nad optymalizacją istniejącej funkcji. Przełączanie optymalizacji lub śledzenie błędu do określonej sekwencji instrukcji jest dla programisty proste - patrz komentarz przed nawiasami klamrowymi:

// if (false) or if (0) 
{
   //experimental optimization  
}

Ta praktyka jest przydatna w pewnych kontekstach, takich jak debugowanie, urządzenia osadzone lub osobisty kod.

kertronux
źródło
10

Zgadzam się z „ruakh”. Jeśli chcesz dobrze wyjaśnić różne poziomy zakresu w C, sprawdź ten post:

Różne poziomy zakresu w aplikacji C.

Zasadniczo użycie „Blokuj zakres” jest pomocne, jeśli chcesz po prostu użyć zmiennej tymczasowej, której nie musisz śledzić przez cały czas trwania wywołania funkcji. Ponadto niektóre osoby używają go, aby dla wygody można było używać tej samej nazwy zmiennej w wielu lokalizacjach, choć ogólnie nie jest to dobry pomysł. na przykład:

int unusedInt = 1;

int main(void) {
  int k;

  for(k = 0; k<10; k++) {
    int returnValue = myFunction(k);
    printf("returnValue (int) is: %d (k=%d)",returnValue,k);
  }

  for(k = 0; k<100; k++) {
    char returnValue = myCharacterFunction(k);
    printf("returnValue (char) is: %c  (k=%d)",returnValue,k);
  }

  return 0;
}

W tym konkretnym przykładzie zdefiniowałem returnValue dwa razy, ale ponieważ jest on tylko w zakresie bloków, a nie w zakresie funkcji (tzn .: zakres funkcji to na przykład deklarowanie returnValue tuż po int main (void)), nie otrzymujesz błędy kompilatora, ponieważ każdy blok jest nieświadomy deklarowanej tymczasowej instancji returnValue.

Nie mogę powiedzieć, że jest to ogólnie dobry pomysł (tj .: prawdopodobnie nie powinieneś ponownie używać nazw zmiennych wielokrotnie z bloku na blok), ale ogólnie oszczędza czas i pozwala uniknąć konieczności zarządzania wartość returnValue w całej funkcji.

Na koniec zwróć uwagę na zakres zmiennych używanych w moim przykładzie kodu:

int:  unusedInt:   File and global scope (if this were a static int, it would only be file scope)
int:  k:           Function scope
int:  returnValue: Block scope
char: returnValue: Block scope
Chmura
źródło
Zajęte pytanie, stary. Nigdy nie miałem 100 wzlotów. Co jest takiego specjalnego w tym pytaniu? Dobry link C jest cenniejszy niż C ++.
Wolfpack'08
5

Dlaczego więc używać „niepotrzebnych” nawiasów klamrowych?

  • Do celów „określania zakresu” (jak wspomniano powyżej)
  • Zwiększenie czytelności kodu (podobnie jak użycie #pragmalub zdefiniowanie „sekcji”, które można wizualizować)
  • Ponieważ możesz. Proste.

PS To nie jest ZŁY kod; jest w 100% poprawny. Jest to raczej kwestia (rzadkiego) smaku.

Dr.Kameleon
źródło
5

Po przejrzeniu kodu w edycji mogę powiedzieć, że niepotrzebne nawiasy klamrowe są prawdopodobnie (w widoku oryginalnych programistów) w 100% jasne, co się stanie podczas if / then, nawet jeśli teraz jest to tylko jedna linia, może to być więcej linii później, a nawiasy kwadratowe gwarantują, że nie popełnisz błędu.

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
   return -1;
}

jeśli powyższe było oryginalne, a usunięcie „dodatków” spowoduje, że:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     return isInit;
   return -1;
}

późniejsza modyfikacja może wyglądać następująco:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     CallSomethingNewHere();
     return isInit;
   return -1;
}

i to oczywiście spowodowałoby problem, ponieważ teraz isInit zawsze będzie zwracany, niezależnie od tego, czy / to.

nycynik
źródło
4

Obiekty są automatycznie niszczone, gdy wykraczają poza zakres ...


źródło
2

Innym przykładem użycia są klasy związane z interfejsem użytkownika, zwłaszcza Qt.

Na przykład masz skomplikowany interfejs użytkownika i wiele widżetów, każdy z nich ma własne odstępy, układ itp. Zamiast nazywać je space1, space2, spaceBetween, layout1, ..., możesz zapisać się przed nieopisowymi nazwami zmiennych, które istnieją tylko w dwóch-trzech wierszach kod.

Cóż, niektórzy mogą powiedzieć, że powinieneś podzielić to na metody, ale tworzenie 40 metod, które nie nadają się do ponownego użycia, nie wygląda dobrze - postanowiłem po prostu dodać nawiasy klamrowe i komentarze przed nimi, więc wygląda to na logiczny blok. Przykład:

// Start video button 
{ 
   <here the code goes> 
}
// Stop video button
{
   <...>
}
// Status label
{
   <...>
}

Nie można powiedzieć, że jest to najlepsza praktyka, ale jest dobra dla starszych kodów.

Wystąpiły te problemy, gdy wiele osób dodało własne komponenty do interfejsu użytkownika, a niektóre metody stały się naprawdę masywne, ale nie jest praktyczne tworzenie 40 jednorazowych metod w klasie, która już się popsuła.

htzfun
źródło