Inicjalizacja zmiennych w instrukcji „if”

80

Czytałem, że w C ++ 17 możemy inicjalizować zmienne w iftakich instrukcjach

if (int length = 2; length == 2)
    //execute something

Zamiast

int length = 2;
if (length == 2)
    //do something

Mimo że jest krótszy, wpływa na czytelność kodu (szczególnie dla osób, które nie znają tej nowej funkcji), co, jak sądzę, jest złą praktyką kodowania w przypadku tworzenia dużego oprogramowania.

Czy korzystanie z tej funkcji ma inne zalety niż skrócenie kodu?

Arne
źródło
38
Oprócz zakresu?
DeiDei
10
Chyba ktoś powiedział kilka lat temu "Czytałem, że w C ++ 11 możemy tworzyć wyrażenia lambda takie jak ta (...) Chociaż jest krótsze, wpływa to na czytelność kodu (szczególnie dla osób, które nie wiedzą ta nowa funkcja), co, jak sądzę, jest złą praktyką kodowania w przypadku tworzenia dużego oprogramowania ”.
R2RT
9
Powiedziałbym, że ma dokładnie taką samą długość, a nie krótszą.
user7860670
5
czysta opinia, stąd nie ma odpowiedzi: if (int length = 2; length == 2)może to zaskakujące, widzisz to za pierwszym razem, ale nie jest to nic skomplikowanego, czego nie można by zrozumieć, więc już za drugim razem nie będzie to już duża niespodzianka i deklarowanie rzeczy w zakresie, do którego należy jeden z głównych czynników wpływających na czytelność. IMHO Twój założeniem jest źle;)
largest_prime_is_463035818
14
Martwienie się o czytelność kodu dla osób, które nie znają języka, w którym kod jest napisany (co oznacza „nie znają tej nowej funkcji”) to wyścig na dno.

Odpowiedzi:

97

Ogranicza zakres lengthdo ifsamego. Otrzymujesz więc te same korzyści, jakie otrzymaliśmy, kiedy pozwolono nam pisać

for(int i = 0; i < ... ; ++i) {
   // ...
}

Zamiast przecieku zmiennej

int i;
for(i = 0; i < ... ; ++i) {
   // ...
}

Zmienne krótkotrwałe są lepsze z kilku powodów. Ale żeby wymienić kilka:

  1. Im krócej coś żyje, tym mniej rzeczy musisz pamiętać podczas czytania niepowiązanych ze sobą wierszy kodu. Jeśli inie istnieje poza pętlą lub ifinstrukcją, nie musimy przejmować się jego wartością poza nimi. Nie musimy też martwić się, że jego wartość będzie oddziaływać z innymi częściami programu, które są poza jego zamierzonym zakresem (co może się zdarzyć, jeśli ipowyższe zostanie ponownie użyte w innej pętli). Ułatwia śledzenie kodu i uzasadnienie.

  2. Jeśli zmienna przechowuje zasób, to jest on teraz przechowywany przez najkrótszy możliwy okres. I to bez obcych kręconych szelek. Wyjaśniono również, że zasób jest powiązany z ifsamym. Potraktuj to jako motywujący przykład

    if(std::lock_guard _(mtx); guarded_thing.is_ready()) {
    }
    

Jeśli Twoi koledzy nie są świadomi tej funkcji, naucz ich! Uspokajanie programistów, którzy nie chcą się uczyć, to kiepska wymówka, aby unikać funkcji.

StoryTeller - Unslander Monica
źródło
12
Chwycę to ostatnie zdanie i wbiję je na dwumetrowy plakat.
Quentin,
3
Ponadto zmienne krótkotrwałe (o wąskim zakresie) powinny zredukować błędy, ponieważ nie można przypadkowo ponownie użyć zmiennej później w kodzie, gdy zostanie ona spełniona.
Galik,
1
Lub ze słabym wskaźnikiem:if (auto p = ptr.lock(); p && p->foo()) bar(*p);
Deduplicator
1
3. W większej liczbie przypadków kompilator może ponownie wykorzystać przestrzeń stosu. (Jeśli kiedykolwiek przekażesz i przez odniesienie lub wskaźnik do funkcji zewnętrznej, na przykład.)
TLW,
Lepszym pytaniem może być „jaka jest przewaga tego w porównaniu z {int i = 2; if (i == 2) {...}}” (zwróć uwagę na dodatkowy zakres).
TLW,
24

Czy korzystanie z tej funkcji ma inne zalety niż skrócenie kodu?

Zmniejszasz zakres zmiennej. Ma to sens i zwiększa czytelność, ponieważ wzmacnia lokalizację identyfikatorów, o których musisz się zastanowić. Zgadzam się, że ifnależy unikać długich instrukcji init w instrukcjach, ale w przypadku krótkich rzeczy jest w porządku.

Zauważ, że możesz już wykonać inicjalizację i rozgałęzienie wyniku w wersji przed C ++ 17:

int *get(); // returns nullptr under some condition

if (int *ptr = get())
    doStuff();

To zależy od osobistej opinii, ale możesz uznać wyraźny warunek za bardziej czytelny:

if (int *ptr = get(); ptr != nullptr)
    doStuff();

Poza tym argumentowanie przeciwko czytelności funkcji poprzez odwoływanie się do faktu, że ludzie nie są do niej przyzwyczajeni, jest niebezpieczne. W pewnym momencie ludzie nie byli przyzwyczajeni do mądrych wskazówek, ale dzisiaj wszyscy zgadzamy się (chyba), że dobrze, że tam są.

lubgr
źródło
4
możesz użyć, if (auto p =get ())ponieważ zdefiniowano operator bool
sudo rm -rf slash
19

Nowa forma instrukcji if ma wiele zastosowań.

Obecnie inicjator jest zadeklarowany przed instrukcją i wyciekł do zakresu otoczenia lub jest używany jawny zakres. Dzięki nowej formie taki kod można napisać bardziej zwięźle, a ulepszona kontrola zakresu sprawia, że ​​niektóre niegdyś podatne na błędy konstrukcje są nieco bardziej niezawodne.

Otwórz standardową propozycję instrukcji If z inicjatorem

wprowadź opis obrazu tutaj

Podsumowując, ta instrukcja upraszcza typowe wzorce kodu i pomaga użytkownikom zachować ścisłe zakresy.

Mam nadzieję, że to pomoże!

Abhishek Sinha
źródło
Czy mógłbyś wyjaśnić, że cytujesz propozycję? Zwłaszcza drugi akapit. Proponuję cytaty.
StoryTeller - Unslander Monica
Dzięki @StoryTeller, tak, zacytowałem drugi akapit z propozycji open-std, która została włączona do C ++ 17.
Abhishek Sinha,
10

W celu zminimalizowania zakresu zmiennych istnieje idiom, który definiuje zasób tylko wtedy, gdy jest ważny w momencie tworzenia (na przykład obiekty strumieni plików ):

if(auto file = std::ifstream("filename"))
{
    // use file here
}
else
{
    // complain about errors here
}

// The identifier `file` does not pollute the wider scope

Czasami chcesz mieć możliwość odwrócenia logiki tego testu, aby błąd stał się klauzulą ​​podstawową, a prawidłowy zasób elseklauzulą. Wcześniej nie było to możliwe. Ale teraz możemy zrobić:

if(auto file = std::ifstream("filename"); !file)
{
    // complain about errors here
}
else
{
    // use file here
}

Przykładem może być zgłoszenie wyjątku:

if(auto file = std::ifstream(filename); !file)
    throw std::runtime_error(std::strerror(errno));
else
{
    // use file here
}

Niektórzy ludzie lubią kodować tak, aby funkcja przerywała działanie wcześniej w przypadku błędu lub w inny sposób postępuje. Ten idiom fizycznie stawia logikę abortowania ponad logiką kontynuacji, którą niektórzy ludzie mogą uznać za bardziej naturalną.

Galik
źródło
8

Jest to szczególnie przydatne w przypadku zdarzeń logicznych. Rozważmy ten przykład:

char op = '-';
if (op != '-' && op != '+' && op != '*' && op != '/') {
    std::cerr << "bad stuff\n";
}

Wydaje się trochę szorstkie. Jeśli nie jesteś bardzo zaznajomiony z OR, ANDnegacjami, być może będziesz musiał zatrzymać się i pomyśleć o tej logice - która jest ogólnie kiepskim projektem. Dzięki if-initializationmożesz dodać wyrazistości.

char op = '-';
if (bool op_valid = (op == '-') || (op == '+') || (op == '*') || (op == '/'); !op_valid) {
    std::cerr << "bad stuff\n";
} 

nazwana zmienna może być ponownie użyta wewnątrz iftoo. Na przykład:

if (double distance = std::sqrt(a * a + b * b); distance < 0.5){
    std::cerr << distance << " is too small\n";
}

Jest to świetne, zwłaszcza biorąc pod uwagę, że zmienna ma określony zakres i dlatego nie zanieczyszcza później przestrzeni.

Stack Danny
źródło
2
Zdaję sobie sprawę, że to subiektywne, ale zdecydowanie wolę twoją "szorstką" wersję od tej z inicjalizatorem if. O wiele łatwiej jest mi to przeczytać i zrozumieć.
Fabio mówi Przywróć Monikę
@FabioTurati Przypuszczam, że dzieje się tak dlatego, że dobrze go znasz, a druga wersja jest nowa. Ale z biegiem czasu spodziewam się, że inicjalizator if pokona coś podobnego.
Stos Danny
7

Jest to rozszerzenie istniejącej funkcji, która zwiększa czytelność w moim doświadczeniu.

if (auto* ptr = get_something()) {
}

Tutaj oboje tworzymy zmienną ptri sprawdzamy, czy nie jest zerowa. Zakres ptrjest ograniczony do miejsca, w którym jest ważny. O wiele łatwiej jest przekonać siebie, że każde użycie ptrjest słuszne.

Ale co, jeśli mówimy o czymś, co nie przekształca się w boolten sposób?

if (auto itr = find(bob)) {
}

To nie działa. Ale dzięki tej nowej funkcji możemy:

if (auto itr = find(bob); itr != end()) {
}

Dodaj klauzulę mówiącą „kiedy ta inicjalizacja jest prawidłowa”.

W istocie daje nam to zestaw tokenów, które oznaczają „zainicjuj jakieś wyrażenie, a kiedy będzie poprawne, wykonaj jakiś kod. Jeśli nie jest poprawne, odrzuć je”.

Od czasu C ++ 98 było idiomatyczne wykonanie sztuczki testującej wskaźnik. Kiedy już to zaakceptujesz, to rozszerzenie jest naturalne.

Yakk - Adam Nevraumont
źródło