Czy auto utrudnia zrozumienie kodu C ++?


122

Widziałem konferencję Herb Suttera, w której zachęca on każdego programistę C ++ do korzystania auto.

Jakiś czas temu musiałem przeczytać kod C #, gdzie varbył intensywnie używany i kod był bardzo trudny do zrozumienia - za każdym razem varmusiałem sprawdzać typ zwrotu po prawej stronie. Czasem więcej niż jeden raz, bo po pewnym czasie zapomniałem o typie zmiennej!

Wiem, że kompilator zna ten typ i nie muszę go pisać, ale powszechnie przyjmuje się, że powinniśmy pisać kod dla programistów, a nie dla kompilatorów.

Wiem też, że łatwiej jest napisać:

auto x = GetX();

Niż:

someWeirdTemplate<someOtherVeryLongNameType, ...>::someOtherLongType x = GetX();

Ale jest to zapisywane tylko raz, a GetX()typ zwracany jest sprawdzany wiele razy, aby zrozumieć, jaki typ xma.

To mnie zastanawiało - czy autotrudniej jest zrozumieć kod C ++?


29
Czy naprawdę musisz za każdym razem sprawdzać typ zwrotu ? Dlaczego czcionka nie jest wyraźna z kodu? autoczęsto sprawia, że ​​rzeczy są trudniejsze do odczytania, gdy są już trudne do odczytania, tj. funkcje zbyt długie, zmienne źle nazwane itp. W przypadku krótkich funkcji z odpowiednio nazwanymi zmiennymi znajomość typów powinna być jedną z 1 łatwych lub 2 nieistotnych.
R. Martinho Fernandes,

25
„Sztuka” używania autojest bardzo podobna do określania, kiedy użyć typedef. Od ciebie zależy, kiedy przeszkodzi, a kiedy pomoże.
ahenderson

18
Myślałem, że mam ten sam problem, ale potem zdałem sobie sprawę, że mogę po prostu zrozumieć kod, nie znając typów. np .: „auto idx = get_index ();” więc idx to coś z indeksem. Dokładny typ jest w większości przypadków zupełnie nieistotny.
PlasmaHH,

31
Więc nie pisz auto x = GetX();, wybierz lepszą nazwę niż ta, xktóra mówi ci, co robi w tym konkretnym kontekście ... i tak często jest bardziej przydatna niż jej typ.
Jonathan Wakely,

11
Jeśli użycie większej liczby wnioskowania typu utrudnia programistom odczytanie kodu, kod lub programista wymaga poważnej poprawy.
CA McCann,

Odpowiedzi:


99

Krótka odpowiedź: Bardziej kompletnie, moim obecnym zdaniem autojest to, że powinieneś używać autodomyślnie, chyba że wyraźnie chcesz konwersji. (Nieco bardziej precyzyjnie: „... chyba że chcesz jawnie zatwierdzić typ, co prawie zawsze dzieje się tak, ponieważ chcesz konwersji”).

Dłuższa odpowiedź i uzasadnienie:

Napisz jawny typ (zamiast auto) tylko wtedy, gdy naprawdę chcesz jawnie zatwierdzić typ, co prawie zawsze oznacza, że ​​chcesz jawnie uzyskać konwersję na ten typ. Z czubka głowy przypominam sobie dwa główne przypadki:

  • (Często) initializer_listNiespodzianka, która auto x = { 1 };wydedukowała initializer_list. Jeśli nie chcesz initializer_list, powiedz typ - tzn. Wyraźnie poproś o konwersję.
  • (Rzadko) Przypadek szablonów wyrażeń, taki jak ten, który auto x = matrix1 * matrix 2 + matrix3;przechwytuje typ pomocnika lub proxy, który nie powinien być widoczny dla programisty. W wielu przypadkach przechwytywanie tego typu jest w porządku i łagodne, ale czasami jeśli naprawdę chcesz, aby zwinął się i wykonał obliczenia, powiedz typ - tj. Ponownie wyraźnie poproś o konwersję.

Rutynowo używaj autodomyślnie w przeciwnym razie, ponieważ użycie autopozwala uniknąć pułapek i sprawia, że ​​kod jest bardziej poprawny, łatwiejszy w utrzymaniu i niezawodny oraz bardziej wydajny. Z grubsza w kolejności od najważniejszych do najmniej ważnych, w duchu „pisz najpierw dla jasności i poprawności”:

  • Prawidłowość: Używając autogwarancji otrzymasz odpowiedni typ. Jak to się mówi, jeśli powtórzysz się (powiedz typ niepotrzebnie), możesz i będziesz kłamać (źle to zrozumiesz). Oto zwykły przykład: void f( const vector<int>& v ) { for( /*…*- w tym momencie, jeśli piszesz typ iteratora wprost, chcesz pamiętać, aby pisać const_iterator(prawda?), Podczas gdy autopo prostu robi to dobrze.
  • Konserwowalność i niezawodność: użycie autopowoduje, że kod staje się bardziej niezawodny w obliczu zmiany, ponieważ gdy typ wyrażenia ulegnie zmianie, autonadal będzie rozpoznawany poprawny typ. Jeśli zamiast tego zdecydujesz się na typ jawny, zmiana typu wyrażenia wprowadzi ciche konwersje, gdy nowy typ konwertuje na stary typ, lub niepotrzebne przerwy w kompilacji, gdy nowy typ nadal działa podobnie jak stary typ, ale nie konwertuje na stary wpisz (na przykład, gdy zmienisz mapna a unordered_map, co jest zawsze w porządku, jeśli nie polegasz na zamówieniu, używając autoiteratorów bezproblemowo przełączysz się map<>::iteratorna unordered_map<>::iterator, ale używającmap<>::iterator wszędzie wyraźnie oznacza to, że będziesz marnować swój cenny czas na falę poprawek kodu, chyba że stażysta przejdzie i nie będziesz w stanie nałożyć na nich nudnej pracy).
  • Wydajność: ponieważ autogwarantuje, że nie dojdzie do niejawnej konwersji, domyślnie gwarantuje lepszą wydajność. Jeśli zamiast tego powiesz typ, który wymaga konwersji, często po cichu uzyskasz konwersję, niezależnie od tego, czy tego oczekiwałeś, czy nie.
  • Użyteczność: Używanie autojest jedyną dobrą opcją dla trudnych do przeliterowania i niewymownych typów, takich jak lambdas i pomocniki szablonów, bez uciekania się do powtarzających się decltypewyrażeń lub mniej wydajnych pośrednich, takich jak std::function.
  • Wygoda: I tak, automniej pisania. Wspominam o tym jako o kompletności, ponieważ jest to powszechny powód, aby go lubić, ale nie jest to największy powód, aby go używać.

Dlatego: Wolę powiedzieć autodomyślnie. Oferuje tyle prostoty, wydajności i przejrzystości, że ranisz siebie (i przyszłych opiekunów kodu) tylko wtedy, gdy tego nie zrobisz. Zatwierdź typ jawny tylko wtedy, gdy naprawdę masz na myśli, co prawie zawsze oznacza, że ​​chcesz jawnej konwersji.

Tak, jest (teraz) GotW na ten temat.


14
Uważam, że auto jest przydatne, nawet jeśli chcę konwersji. Pozwala mi to wyraźnie poprosić o konwersji bez powtarzania typ: auto x = static_cast<X>(y). static_castJasno wynika, że konwersja jest celowo i unika ostrzeżenia kompilatora o konwersji. Zwykle unikanie ostrzeżeń kompilatora nie jest tak dobre, ale nic mi nie jest, gdy nie otrzymuję ostrzeżenia o konwersji, które starannie rozważałem, pisząc static_cast. Chociaż nie zrobiłbym tego, gdyby nie było teraz ostrzeżeń, ale chcę otrzymywać ostrzeżenia w przyszłości, jeśli typy zmienią się w potencjalnie niebezpieczny sposób.
Bjarke Hammersholt Roune

6
Jedną z rzeczy, które uważam, autojest to, że powinniśmy starać się programować przeciwko interfejsom (nie w sensie OOP), a nie przeciwko konkretnym implementacjom. Tak samo jest z szablonami. Czy narzekasz na „trudny do odczytania kod”, ponieważ masz parametr typu szablonu, Tktóry jest używany wszędzie? Nie, nie sądzę. Również w szablonach kodujemy na interfejsie, wiele osób nazywa to pisaniem w czasie kompilacji.
Xeo

6
„Korzystanie z automatycznych gwarancji zapewni odpowiedni typ”. Zupełnie nieprawda. Gwarantuje to tylko, że otrzymasz typ zalecany przez inną część kodu. To, czy to prawda, czy nie, jest całkowicie niejasne, gdy ukrywasz ją za sobą auto.
Wyścigi lekkości na orbicie

Jestem naprawdę zaskoczony, że nikt nie dba o IDE ... Nawet współczesne IDE nie obsługują poprawnie przejścia do definicji klasy / struktury w przypadku autozmiennej, ale prawie wszystkie z nich robią to poprawnie z wyraźną specyfikacją typu. Nikt nie używa IDE? Czy wszyscy używają tylko zmiennych int / float / bool? Czy wszyscy wolą zewnętrzną dokumentację bibliotek zamiast samodokumentowanych nagłówków?
avtomaton

że GotW: herbutter.com/2013/08/12/ ... Nie rozumiem, jak ta „inicjalizator_list niespodzianka” jest niespodzianką; nawiasy klamrowe wokół =RHS nie mają większego sensu w żadnej innej interpretacji (lista inicjująca z prętami, ale musisz wiedzieć, co inicjujesz, z czym jest oksymoronem auto). Ten, który jest zaskakujący, auto i{1}również wydedukuje initializer_list, pomimo sugerowania, że ​​nie bierz tej spiętej listy inicjującej, ale raczej weź to wyrażenie i użyj jego typu ... ale my initializer_listteż tam jesteśmy . Na szczęście C ++ 17 dobrze to naprawia.
underscore_d

112

Jest to sytuacja indywidualna.

Czasami utrudnia to zrozumienie kodu, a czasem nie. Weź na przykład:

void foo(const std::map<int, std::string>& x)
{
   for ( auto it = x.begin() ; it != x.end() ; it++ )
   { 
       //....
   }
}

jest zdecydowanie łatwy do zrozumienia i zdecydowanie łatwiejszy do napisania niż rzeczywista deklaracja iteratora.

Używam C ++ już od jakiegoś czasu, ale mogę zagwarantować, że przy pierwszym uruchomieniu dostanę błąd kompilatora, ponieważ zapomnę o tym const_iteratori początkowo pójdę na iterator... :)

Użyłbym tego do takich przypadków, ale nie tam, gdzie faktycznie zaciemnia typ (jak twoja sytuacja), ale jest to czysto subiektywne.


45
Dokładnie. Kogo obchodzi ten typ. To iterator. Nie dbam o typ, wszystko co muszę wiedzieć to, że mogę go używać do iteracji.
R. Martinho Fernandes,

5
+1. Nawet jeśli nazwałeś typ, nazwałbyś go jako std::map<int, std::string>::const_iterator, więc to nie tak, że nazwa i tak wiele mówi o typie.
Steve Jessop,

4
@ SteveJessop: Mówi mi co najmniej dwie rzeczy: klucz jest int, a wartość jest std::string. :)
Nawaz

16
@Nawaz: i do którego nie można przypisać, it->secondponieważ jest to ciągły iterator. Wszystkie informacje, które jest powtórzeniem tego, co znajduje się w poprzednim wierszu const std::map<int, std::string>& x. Wielokrotne mówienie rzeczy czasami lepiej informuje, ale w żadnym wypadku nie jest to ogólna zasada :-)
Steve Jessop,

11
TBH Wolę for (anX : x)sprawić, by stało się jeszcze bardziej oczywiste, że właśnie się powtarzamy x. Normalnym przypadkiem, w którym potrzebujesz iteratora, jest modyfikowanie kontenera, ale xjest toconst&
MSalters

94

Spójrz na to z innej strony. Czy ty piszesz:

std::cout << (foo() + bar()) << "\n";

lub:

// it is important to know the types of these values
int f = foo();
size_t b = bar();
size_t total = f + b;

std::cout << total << "\n";

Czasami nie pomaga to przeliterować tego typu.

Decyzja, czy należy podać typ, nie jest tym samym, co decyzja o podzieleniu kodu na wiele instrukcji poprzez zdefiniowanie zmiennych pośrednich. W C ++ 03 oba były ze sobą powiązane, można pomyśleć o autosposobie ich rozdzielenia.

Czasami przydatne może być wyraźne określenie typów:

// seems legit    
if (foo() < bar()) { ... }

vs.

// ah, there's something tricky going on here, a mixed comparison
if ((unsigned int)foo() < bar()) { ... }

W przypadkach, w których deklarujesz zmienną, użycie autopozwala na wypisanie typu niewypowiedzianego, tak jak w wielu wyrażeniach. Prawdopodobnie powinieneś sam zdecydować, kiedy to poprawi czytelność, a kiedy utrudni.

Można argumentować, że mieszanie typów podpisanych i niepodpisanych jest błędem na początku (w rzeczywistości niektórzy twierdzą dalej, że nie należy w ogóle używać typów niepodpisanych). Powodem jest zapewne błędem jest to, że sprawia, że typy argumentów niezwykle ważne ze względu na różne zachowania. Jeśli źle jest znać typy swoich wartości, prawdopodobnie nie jest też złą rzeczą nie znać ich. Więc pod warunkiem, że kod nie jest już mylący z innych powodów, to czyni autoOK, prawda? ;-)

Szczególnie przy pisaniu kodu ogólnego zdarzają się przypadki, w których faktyczny typ zmiennej nie powinien być ważny, ważne jest to, że spełnia on wymagany interfejs. Więc autozapewnia poziom abstrakcji gdzie zignorować typ (ale oczywiście kompilator nie, to wie). Praca na odpowiednim poziomie abstrakcji może znacznie poprawić czytelność, praca na „złym” poziomie sprawia, że ​​czytanie kodu jest hasłem.


21
+1 autopozwala na tworzenie nazwanych zmiennych o nienazwanych lub nieciekawych typach. Znaczące nazwy mogą być przydatne.
Mankarse,

Mieszanie ze znakiem i bez znaku, jeśli używasz niepodpisanego do właściwego zastosowania: arytmetyka modularna. Nie dzieje się tak, jeśli niewłaściwie użyjesz niepodpisanego dla dodatniej liczby całkowitej. Prawie żaden program nie ma zastosowania do niepodpisanego, ale podstawowy język wymusza sizeofna tobie szaloną definicję „ bez znaku”.
ciekawy

27

IMO, patrzysz na to prawie odwrotnie.

Nie chodzi o autodoprowadzenie do kodu, który jest nieczytelny lub nawet mniej czytelny. Jest to kwestia (mając nadzieję, że) wyraźnego typu wartości zwracanej zrekompensuje fakt, że (najwyraźniej) nie jest jasne, jaki typ zostałby zwrócony przez jakąś określoną funkcję.

Przynajmniej moim zdaniem, jeśli masz funkcję, której typ zwrotu nie jest od razu oczywisty, to właśnie tam masz problem. To, co robi funkcja, powinno wynikać z jej nazwy, a rodzaj zwracanej wartości powinien wynikać z jej działania. Jeśli nie, to prawdziwe źródło problemu.

Jeśli jest tu problem, to nie jest auto. Z resztą kodu i są całkiem spore szanse, że jawny typ jest wystarczającą pomocą pasma, aby powstrzymać cię od dostrzeżenia i / lub naprawienia podstawowego problemu. Po naprawieniu tego prawdziwego problemu czytelność używanego kodu autobędzie na ogół w porządku.

Przypuszczam, że uczciwie powinienem dodać: miałem do czynienia z kilkoma przypadkami, w których takie rzeczy nie były tak oczywiste, jak byś chciał, a naprawienie problemu również było nie do zniesienia. Dla przykładu, kilka lat temu przeprowadziłem konsultacje dla firmy, która wcześniej połączyła się z inną firmą. W rezultacie powstała baza kodu, która była bardziej „pchana razem” niż połączona. Programy składowe zaczęły używać różnych (ale całkiem podobnych) bibliotek do podobnych celów, i chociaż pracowały nad tym, aby scalić wszystko w bardziej przejrzysty sposób, nadal tak było. W sporej liczbie przypadków jedynym sposobem na odgadnięcie, jaki typ zostanie zwrócony przez daną funkcję, było poznanie jej pochodzenia.

Nawet w takim przypadku możesz pomóc w wyjaśnieniu kilku rzeczy. W takim przypadku cały kod zaczął się w globalnej przestrzeni nazw. Po prostu przeniesienie sporej kwoty do niektórych przestrzeni nazw wyeliminowało konflikty nazw i znacznie ułatwiło śledzenie typów.


17

Istnieje kilka powodów, dla których nie lubię auto do ogólnego użytku:

  1. Możesz refaktoryzować kod bez jego modyfikacji. Tak, jest to jedna z często wymienianych zalet korzystania z auto. Wystarczy zmienić typ zwracanej funkcji, a jeśli cały kod, który ją wywołuje, używa auto, nie jest wymagany żaden dodatkowy wysiłek! Uderzasz w kompilację, buduje - 0 ostrzeżeń, 0 błędów - i po prostu sprawdzasz kod bez konieczności zajmowania się bałaganem przeglądania i potencjalnej modyfikacji 80 miejsc, w których funkcja jest używana.

Ale czekaj, czy to naprawdę dobry pomysł? Co jeśli typ miał znaczenie w pół tuzinie przypadków użycia, a teraz ten kod zachowuje się inaczej? Może to również pośrednio przerwać enkapsulację, modyfikując nie tylko wartości wejściowe, ale także samo zachowanie prywatnej implementacji innych klas wywołujących funkcję.

1a. Jestem zwolennikiem koncepcji „samodokumentującego się kodu”. Kod samo-dokumentujący ma swoje uzasadnienie w tym, że komentarze stają się nieaktualne, nie odzwierciedlają już tego, co robi kod, podczas gdy sam kod - jeśli jest napisany w sposób jawny - jest zrozumiały, zawsze jest aktualny zgodnie z jego intencją i nie pozostawi cię mylonych ze starymi komentarzami. Jeśli typy można zmienić bez potrzeby modyfikowania samego kodu, wówczas sam kod / zmienne mogą stać się nieaktualne. Na przykład:

auto bThreadOK = CheckThreadHealth ();

Tyle tylko, że problem polega na tym, że CheckThreadHealth () w pewnym momencie został refaktoryzowany w celu zwrócenia wartości wyliczeniowej wskazującej ewentualny status błędu zamiast bool. Ale osoba, która wprowadziła tę zmianę, nie sprawdzała tego konkretnego wiersza kodu, a kompilator nie pomógł, ponieważ skompilował się bez ostrzeżeń i błędów.

  1. Możesz nigdy nie wiedzieć, jakie są rzeczywiste typy. Jest to często wymieniane jako podstawowa „korzyść” z auto. Po co uczyć się, co daje ci funkcja, kiedy możesz po prostu powiedzieć: „Kogo to obchodzi? To się kompiluje!”

Prawdopodobnie nawet działa. Mówię, że to działa, ponieważ chociaż tworzysz kopię struktury 500-bajtowej dla każdej iteracji pętli, więc możesz sprawdzić na niej jedną wartość, kod jest nadal w pełni funkcjonalny. Więc nawet testy jednostkowe nie pomagają zrozumieć, że za tym prostym i niewinnie wyglądającym auto kryje się zły kod. Większość innych osób skanujących plik również nie zauważy go na pierwszy rzut oka.

Można to również pogorszyć, jeśli nie wiesz, jaki jest typ, ale wybierasz nazwę zmiennej, która błędnie zakłada, co to jest, w efekcie osiągając taki sam wynik jak w 1a, ale od samego początku, a nie postfaktor.

  1. Wpisywanie kodu na początku nie jest najbardziej czasochłonnym etapem programowania. Tak, auto przyspiesza początkowo pisanie kodu. Jako zastrzeżenie, wpisuję> 100 WPM, więc może nie przeszkadza mi tak bardzo jak innym. Ale gdybym tylko musiał cały dzień pisać nowy kod, byłbym szczęśliwym obozowiczem. Najbardziej czasochłonną częścią programowania jest diagnozowanie trudnych do odtworzenia, niepoprawnych błędów w kodzie, które często wynikają z subtelnych, nieoczywistych problemów - takich jak nadużycie auto, które może się pojawić (odwołanie vs. kopiowanie, podpisane vs. niepodpisane, float vs. int, bool vs. wskaźnik itp.).

Wydaje mi się oczywiste, że auto zostało wprowadzone przede wszystkim jako obejście strasznej składni ze standardowymi typami szablonów bibliotek. Zamiast próbować naprawić składnię szablonu, którą ludzie już znają - co może być prawie niemożliwe do wykonania z powodu całego istniejącego kodu, który mógłby złamać - dodaj słowo kluczowe, które w zasadzie ukrywa problem. Zasadniczo to, co możesz nazwać „hack”.

Właściwie nie mam żadnych sporów z użyciem auto ze standardowymi kontenerami bibliotecznymi. Oczywiście dla tego słowa kluczowego zostało utworzone, a funkcje w standardowej bibliotece prawdopodobnie nie zmienią się zasadniczo w celu (lub typowaniu w tym zakresie), dzięki czemu korzystanie z auto jest względnie bezpieczne. Byłbym jednak bardzo ostrożny w używaniu go z własnym kodem i interfejsami, które mogą być znacznie bardziej zmienne i potencjalnie podlegać bardziej fundamentalnym zmianom.

Inną użyteczną aplikacją auto, która zwiększa możliwości języka, jest tworzenie tymczasowych makr typu agnostic. To było coś, czego tak naprawdę nie mogłeś zrobić wcześniej, ale możesz to zrobić teraz.


4
Udało wam się. Chciałbym dać to +2.
cmaster

Dobra odpowiedź „bądź cholernie ostrożna”. @cmaster: Tam jest.
Deduplicator,

Znalazłem jeden bardziej użyteczne sprawy: auto something = std::make_shared<TypeWithLongName<SomeParam>>(a,b,c);. :-)
Notinlist,

14

Tak, łatwiej jest poznać typ zmiennej, jeśli nie jest używana auto. Pytanie brzmi: czy musisz znać typ swojej zmiennej, aby odczytać kod? Czasami odpowiedź będzie twierdząca, a czasem nie. Na przykład, kiedy otrzymujesz iterator z std::vector<int>, czy musisz wiedzieć, że jest to std::vector<int>::iteratorlub auto iterator = ...;wystarczy? Wszystko, co każdy chciałby zrobić z iteratorem, wynika z faktu, że jest to iterator - nie ma znaczenia, jaki typ jest konkretny.

Używaj autow sytuacjach, gdy nie utrudnia to odczytania kodu.


12

Osobiście używam autotylko wtedy, gdy jest to absolutnie oczywiste dla programisty, co to jest.

Przykład 1

std::map <KeyClass, ValueClass> m;
// ...
auto I = m.find (something); // OK, find returns an iterator, everyone knows that

Przykład 2

MyClass myObj;
auto ret = myObj.FindRecord (something)// NOT OK, everyone needs to go and check what FindRecord returns

5
Jest to wyraźny przykład złej nazwy, która szkodzi czytelności, a nie tak naprawdę auto. Nikt nie ma bladego pojęcia, co robi „DoSomethingWeird”, więc użycie auto lub nie spowoduje, że nie będzie bardziej czytelny. W obu przypadkach będziesz musiał sprawdzić dokumenty.
R. Martinho Fernandes,

4
Ok, teraz jest trochę lepiej. Nadal uważam, że zmienna jest źle nazwana, co nadal boli. Gdybyś napisał auto record = myObj.FindRecord(something), byłoby jasne, że typ zmiennej był rekordowy. Lub nazwanie go itlub podobnego sprawi, że będzie jasne, że zwraca iterator. Zauważ, że nawet jeśli nie używałeś auto, prawidłowe nazewnictwo zmiennej oznaczałoby, że nie musisz wracać do deklaracji, aby spojrzeć na typ z dowolnego miejsca w funkcji . Usunąłem moją opinię, ponieważ ten przykład nie jest teraz kompletnym głupkiem, ale nadal nie kupuję tutaj argumentu.
R. Martinho Fernandes,

2
Aby dodać do @ R.MartinhoFernandes: pytanie brzmi: czy naprawdę jest teraz ważne CO TO właściwie jest „rekord”? Wydaje mi się, że ważniejsze jest to, że jest to zapis, a faktyczny podstawowy typ prymitywny jest kolejną warstwą abstrakcji. Więc bez auto prawdopodobnie prawdopodobnie:MyClass::RecordTy record = myObj.FindRecord (something)
paul23

2
@ paul23: Co zyskuje użycie auto w porównaniu z typem, jeśli twoim jedynym zastrzeżeniem jest „nie wiem, jak tego używać”. Albo każe ci to sprawdzić.
GManNickG,

3
@GManNickG mówi mi dokładnie o rodzaju nieistotności.
pa2323

10

To pytanie wymaga opinii, które będą się różnić w zależności od programisty, ale powiedziałbym, że nie. W rzeczywistości w wielu przypadkach wręcz przeciwnie, automoże pomóc w zrozumieniu kodu, pozwalając programiście skupić się na logice, a nie na szczegółach.

Jest to szczególnie prawdziwe w przypadku złożonych typów szablonów. Oto uproszczony i przemyślany przykład. Co jest łatwiejsze do zrozumienia?

for( std::map<std::pair<Foo,Bar>, std::pair<Baz, Bot>, std::less<BazBot>>::const_iterator it = things_.begin(); it != things_.end(); ++it )

.. lub ...

for( auto it = things_.begin(); it != things_.end(); ++it )

Niektórzy twierdzą, że drugi jest łatwiejszy do zrozumienia, inni mogą powiedzieć pierwszy. Jeszcze inni mogą powiedzieć, że nieuzasadnione użycie automoże przyczynić się do ogłuszenia programistów, którzy go używają, ale to już inna historia.


4
+1 Haha, wszyscy prezentują std::mapprzykłady, dodatkowo ze złożonymi argumentami szablonu.
Nawaz,

1
@Nawaz: Łatwo jest wymyślić szalone długie nazwy szablonów za pomocą maps. :)
John Dibling,

@Nawaz: ale zastanawiam się, dlaczego nikt nie przychodzi z zasięgiem opartym na pętlach jako lepszej i bardziej czytelnej alternatywie ...
PlasmaHH

1
@PlasmaHH, nie wszystkie pętle z iteratorami można zastąpić opartymi na zakresie, fornp. Jeśli iteratory są unieważnione w ciele pętli i dlatego muszą być wstępnie zwiększane lub w ogóle nie zwiększane.
Jonathan Wakely,

@PlasmaHH: W moim przypadku MSVC10 nie robi pętli na podstawie zakresu. Ponieważ MSVC10 to moja podstawowa wersja testowa C ++ 11, tak naprawdę nie mam z nimi dużego doświadczenia.
John Dibling,

8

Wiele dobrych odpowiedzi do tej pory, ale aby skupić się na pierwotnym pytaniu, myślę, że Herb posuwa się za daleko w swoich radach, aby korzystać z niego autoswobodnie. Twój przykład to jeden przypadek, w którym użycie autooczywiście szkodzi czytelności. Niektórzy twierdzą, że nie ma problemu z nowoczesnymi IDE, w których można najechać wskaźnikiem na zmienną i zobaczyć typ, ale nie zgadzam się: nawet ludzie, którzy zawsze używają IDE, czasami muszą patrzeć na fragmenty kodu w izolacji (pomyśl o recenzjach kodu , na przykład), a IDE nie pomoże.

Konkluzja: użyj, autogdy pomaga: tj. Iteratory dla pętli. Nie używaj go, gdy zmusza czytelnika do znalezienia typu.


6

Jestem dość zaskoczony, że nikt jeszcze nie zauważył, że auto pomaga, jeśli nie ma wyraźnego typu. W takim przypadku możesz albo obejść ten problem, używając #define lub typedef w szablonie, aby znaleźć rzeczywisty typ użyteczny (i czasami nie jest to trywialne), lub po prostu użyj auto.

Załóżmy, że masz funkcję, która zwraca coś z typem platformy:

#ifdef PLATFROM1
__int256 getStuff();
#else //PLATFORM2
__int128 getStuff();
#endif

Wolisz używać czarownic?

#ifdef PLATFORM1
__int256 stuff = getStuff();
#else
__int128 stuff = getStuff();
#endif

lub po prostu

auto stuff = getStuff();

Jasne, możesz pisać

#define StuffType (...)

gdzieś też, ale robi

StuffType stuff = getStuff();

faktycznie powiedzieć coś więcej o typie x? Mówi, że jest to, co jest stamtąd zwracane, ale jest dokładnie tym, czym jest auto. Jest to po prostu zbędne - „rzeczy” są tutaj pisane 3 razy - to moim zdaniem czyni go mniej czytelnym niż wersja „auto”.


5
Właściwy sposób obsługi określonych typów platform to dla typedefnich.
cmaster

3

Czytelność jest subiektywna; musisz spojrzeć na sytuację i zdecydować, co jest najlepsze.

Jak zauważyłeś, bez auto długie deklaracje mogą powodować dużo bałaganu. Jednak, jak wskazałeś, krótkie deklaracje mogą usuwać informacje o typie, które mogą być cenne.

Ponadto dodam jeszcze: upewnij się, że patrzysz na czytelność, a nie na pisalność. Kod, który łatwo jest napisać, ogólnie nie jest łatwy do odczytania i odwrotnie. Na przykład, gdybym pisał, wolałbym auto. Gdybym czytał, może dłuższe deklaracje.

Potem jest konsekwencja; jak to dla ciebie ważne? Czy chciałbyś mieć auto w niektórych częściach i wyraźne deklaracje w innych, czy jedną spójną metodę?


2

Zaletą będzie mniej czytelny kod i zachęcę programistę do korzystania z niego coraz częściej. Dlaczego? Oczywiście, jeśli kod korzystający z auto jest trudny do odczytania, wówczas trudno będzie również napisać. Programista jest zmuszony użyć znaczącej nazwy zmiennej , aby poprawić swoją pracę.
Być może na początku programista może nie pisać znaczących nazw zmiennych. Ale ostatecznie, gdy naprawia błędy lub podczas przeglądu kodu, gdy musi wyjaśnić kod innym lub w niedalekiej przyszłości, wyjaśniając kod konserwatorom, programista zda sobie sprawę z błędu i użyje znacząca nazwa zmiennej w przyszłości.


2
W najlepszym przypadku ludzie piszą nazwy zmiennych, np. W myComplexDerivedTypecelu uzupełnienia brakującego typu, co zaśmieca kod powtarzaniem typu (wszędzie, gdzie używana jest zmienna) i zachęca ludzi do pominięcia celu zmiennej w jej nazwie . Z mojego doświadczenia wynika, że ​​nie ma nic tak nieproduktywnego jak aktywne stawianie przeszkód w kodzie.
cmaster

2

Mam dwie wskazówki:

  • Jeśli typ zmiennej jest oczywisty, żmudne w pisaniu lub trudne do ustalenia użyj auto.

    auto range = 10.0f; // Obvious
    
    for (auto i = collection.cbegin(); i != cbegin(); ++i) // Tedious if collection type
    // is really long
    
    template <typename T> ... T t; auto result = t.get(); // Hard to determine as get()
    // might return various stuff
  • Jeśli potrzebujesz konkretnej konwersji lub typ wyniku nie jest oczywisty i może powodować zamieszanie.

    class B : A {}; A* foo = new B(); // 'Convert'
    
    class Factory { public: int foo(); float bar(); }; int f = foo(); // Not obvious

0

Tak. Zmniejsza to gadatliwość, ale częstym nieporozumieniem jest to, że gadatliwość zmniejsza czytelność. Jest to prawdą tylko wtedy, gdy uważasz, że czytelność jest bardziej estetyczna niż faktyczna zdolność do interpretowania kodu - która nie jest zwiększana przez użycie auto. W najczęściej cytowanym przykładzie, iteratorach wektorowych, na powierzchni może się wydawać, że użycie auto zwiększa czytelność kodu. Z drugiej strony nie zawsze wiesz, co da ci słowo kluczowe auto. Musisz wykonać tę samą logiczną ścieżkę, co kompilator, aby dokonać wewnętrznej rekonstrukcji i przez większość czasu, szczególnie w przypadku iteratorów, będziesz przyjmować błędne założenia.

Pod koniec dnia „auto” poświęca czytelność kodu i klarowność, dla syntaktycznej i estetycznej „czystości” (co jest konieczne tylko dlatego, że iteratory mają niepotrzebnie skomplikowaną składnię) oraz możliwość wpisania 10 mniej znaków w dowolnym wierszu. Nie warto ryzykować ani długofalowego wysiłku.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.