Wygląda na to, że auto
była to dość istotna funkcja do dodania w C ++ 11, która wydaje się być zgodna z wieloma nowszymi językami. Podobnie jak w przypadku języka takiego jak Python, nie widziałem żadnej jawnej deklaracji zmiennej (nie jestem pewien, czy jest to możliwe przy użyciu standardów Pythona).
Czy istnieje wada używania auto
do deklarowania zmiennych zamiast ich jawnego deklarowania?
c++
c++11
type-inference
auto
DxAlpha
źródło
źródło
Odpowiedzi:
Pytałeś tylko o wady, więc podkreślam niektóre z nich. Dobrze stosowany
auto
ma również kilka zalet. Wady wynikają z łatwości nadużyć i zwiększonego potencjału kodu do zachowywania się w niezamierzony sposób.Główną wadą jest to, że używając
auto
, niekoniecznie znasz typ tworzonego obiektu. Istnieją również sytuacje, w których programista może oczekiwać, że kompilator wydedukuje jeden typ, ale kompilator stanowczo wywnioskuje inny.Biorąc pod uwagę deklarację typu
niekoniecznie masz wiedzę o tym, jaki
result
to jest typ . Może to być plikint
. To może być wskaźnik. To może być coś innego. Wszystkie obsługują różne operacje. Możesz także radykalnie zmienić kod poprzez niewielką zmianę, taką jakponieważ w zależności od tego, jakie przeciążenia istnieją, dla
CallSomeFunction()
typu wynik może być zupełnie inny - a kolejny kod może zatem zachowywać się zupełnie inaczej niż zamierzano. Możesz nagle wywołać komunikaty o błędach w późniejszym kodzie (np. Później próba wyłuskiwaniaint
, próba zmiany czegoś, co jest terazconst
). Bardziej złowrogą zmianą jest to, że Twoja zmiana przepływa obok kompilatora, ale późniejszy kod zachowuje się w inny i nieznany - prawdopodobnie błędny - sposób.Brak wyraźnej wiedzy o typie niektórych zmiennych utrudnia zatem rygorystyczne uzasadnienie twierdzenia, że kod działa zgodnie z przeznaczeniem. Oznacza to więcej wysiłku, aby uzasadnić twierdzenia o „przydatności do celu” w dziedzinach o wysokim stopniu krytyczności (np. Krytycznych dla bezpieczeństwa lub krytycznych dla misji).
Inną, bardziej powszechną wadą, jest pokusa dla programisty, aby użyć go
auto
jako tępego narzędzia do wymuszenia kompilacji kodu, zamiast myśleć o tym, co robi kod i pracować nad tym, aby to dobrze.źródło
auto
, to większość języków pisanych na kaczce ma taką wadę z założenia!CallSomeFunction()
zwraca inny typ w zależności od sekwencji argumentów, jest to wada projektuCallSomeFunction()
, a nie problemauto
. Jeśli nie przeczytasz dokumentacji funkcji, której używasz, przed jej użyciem, jest to wada programisty, a nie problemauto
. - Ale rozumiem, że grasz tutaj adwokata diabła, po prostu Nir Friedman ma o wiele lepszy argument.T CallSomeFunction(T, int, int)
być defekt projektu? Oczywiście „zwraca inny typ w zależności od kolejności argumentów”.auto
, niekoniecznie znasz typ tworzonego obiektu”. Czy możesz wyjaśnić, dlaczego jest to problem zauto
tymczasowymi podwyrażeniami, a nie z nimi? Dlaczego jestauto result = foo();
źle, alefoo().bar()
nie?W zasadzie nie jest to wada
auto
, ale w praktyce wydaje się to być problemem dla niektórych. Zasadniczo niektórzy ludzie albo: a) traktująauto
jako zbawiciela dla typów i wyłączają mózg, gdy go używają, lub b) zapominają, żeauto
zawsze dedukuje do typów wartości. To powoduje, że ludzie robią takie rzeczy:Ups, właśnie głęboko skopiowaliśmy jakiś obiekt. Często jest to błąd lub awaria wydajności. Następnie możesz też wychylić się w drugą stronę:
Teraz masz wiszące odniesienie. Te problemy wcale nie są powodowane przez
auto
, więc nie uważam ich za uzasadnione argumenty przeciwko temu. Ale wydaje się, żeauto
sprawia, że ten problem jest bardziej powszechny (z mojego osobistego doświadczenia), z powodów, które wymieniłem na początku.Myślę, że w miarę upływu czasu ludzie dostosują się i zrozumieją podział pracy:
auto
wyprowadza podstawowy typ, ale nadal chcesz pomyśleć o referencji i stałości. Ale to zajmuje trochę czasu.źródło
std::vector
). Kosztowność kopiowania nie jest właściwością klasy, ale poszczególnych obiektów.method_that_returns_reference
Może więc odnosić się do obiektu klasy, który ma konstruktora kopiującego, ale który jest dość drogi do skopiowania (i nie można go przenieść).std::vector
? (Ponieważ może tak, lub dlatego, że nie kontrolujesz klasy, ale nie o to chodzi). Jeśli kopiowanie jest drogie (i nie posiada żadnego zasobu, ponieważ można go skopiować), dlaczego nie użyć COW na obiekcie? Lokalność danych jest już zabijana przez rozmiar obiektu.= delete
to przeciążenie. Chociaż bardziej ogólnie to, co mówisz, jest rozwiązaniem. To jest temat, który zbadałem, jeśli jesteś zainteresowany: nirfriedman.com/2016/01/18/… .Inne odpowiedzi wspominają o wadach, takich jak „tak naprawdę nie wiesz, jaki jest typ zmiennej”. Powiedziałbym, że jest to w dużej mierze związane z niechlujną konwencją nazewnictwa w kodzie. Jeśli twoje interfejsy są wyraźnie nazwane, nie powinieneś przejmować się dokładnym typem. Jasne,
auto result = callSomeFunction(a, b);
niewiele ci mówi. Aleauto valid = isValid(xmlFile, schema);
mówi wystarczająco dużo, aby użyćvalid
bez martwienia się, jaki jest jego dokładny typ. W końcu przy zwykłymif (callSomeFunction(a, b))
też nie znałbyś tego typu. To samo z innymi obiektami tymczasowymi podwyrażeń. Więc nie uważam tego za prawdziwą wadęauto
.Powiedziałbym, że jego główną wadą jest to, że czasami dokładny typ zwrotu nie jest tym, z czym chcesz pracować. W efekcie czasami rzeczywisty typ zwracany różni się od „logicznego” typu zwracanego ze względu na szczegóły dotyczące implementacji / optymalizacji. Najlepszym przykładem są szablony wyrażeń. Powiedzmy, że mamy to:
Logicznie rzecz biorąc, spodziewalibyśmy
SomeType
się , że tak będzieVector
i zdecydowanie chcemy traktować to jako takie w naszym kodzie. Jednak możliwe jest, że dla celów optymalizacji biblioteka algebry, której używamy, implementuje szablony wyrażeń, a rzeczywisty typ zwracania jest następujący:Otóż, problem polega na tym
MultExpression<Matrix, Vector>
, że najprawdopodobniej będzie przechowywać aconst Matrix&
iconst Vector&
wewnętrznie; oczekuje, że przekształci się w aVector
przed końcem pełnego wyrażenia. Jeśli mamy ten kod, wszystko jest w porządku:Gdybyśmy jednak
auto
tutaj skorzystali , moglibyśmy mieć kłopoty:źródło
auto
ma bardzo niewiele wad, więc trzymam się tej siły. Inne przykłady serwerów proxy itp. Obejmują różne „konstruktory łańcuchów” i podobne obiekty znajdujące się w DSL.auto
wcześniej, szczególnie z biblioteką Eigen. Jest to szczególnie trudne, ponieważ problem często nie pojawia się w kompilacjach do debugowania.auto
może również ugryźć podczas korzystania z biblioteki macierzy Armadillo , która w dużym stopniu wykorzystuje metaprogramowanie szablonów do celów optymalizacji. Na szczęście programiści dodali funkcję .eval (), której można użyć, aby uniknąć problemów zauto
auto
generalnie wymaga pewnego rodzaju sprawdzania typu (a rozpryskiwanie sięauto
wszędzie usuwa tego typu bezpieczeństwo, tak jak wszędzie indziej). To nie jest dobre porównanie.Jedną z wad jest to, że czasami nie można zadeklarować
const_iterator
zauto
. Otrzymasz zwykły (inny niż stały) iterator w tym przykładzie kodu zaczerpniętego z tego pytania :źródło
iterator
w każdym razie otrzymujesz, ponieważ twoja mapa nie jestconst
. jeśli chcesz przekonwertować go na aconst_iterator
, albo jawnie określ typ zmiennej jak zwykle lub wyodrębnij metodę, aby mapa była stała w kontekście twojegofind
. (Wolałbym to drugie. SRP.)auto city_it = static_cast<const auto&>(map).find("New York")
? albo z C ++ 17auto city_if = std::as_const(map).find("New York")
.To sprawia, że twój kod jest trochę trudniejszy lub uciążliwy do odczytania. Wyobraź sobie coś takiego:
Teraz, aby ustalić typ wyniku, musiałbyś wyśledzić sygnaturę
doSomethingWithData
funkcji.źródło
auto it = vec.begin();
jest dużo łatwiejszy do odczytania niżstd::vector<std::wstring>::iterator it = vec.begin();
na przykład.Podobnie jak ten programista, nienawidzę
auto
. A raczej nienawidzę tego, jak ludzie nadużywająauto
.Jestem (mocną) opinią, że
auto
pomaga ci pisać ogólny kod, a nie redukuje pisanie .C ++ to język, którego celem jest umożliwienie pisania niezawodnego kodu, a nie minimalizacja czasu programowania.
Jest to dość oczywiste z wielu funkcji C ++, ale niestety kilka nowszych, takich jak
auto
ten, ogranicza pisanie na klawiaturze, co prowadzi ludzi do myślenia, że powinni zacząć leniwie pisać.W dawnych
auto
czasach ludzie używalitypedef
s, co było świetne, ponieważtypedef
pozwalało projektantowi biblioteki pomóc ci ustalić, jaki powinien być typ zwrotu, tak aby ich biblioteka działała zgodnie z oczekiwaniami. Kiedy używaszauto
, odbierasz tę kontrolę projektantowi klasy i zamiast tego prosisz kompilator o ustalenie , jaki powinien być typ, co usuwa jedno z najpotężniejszych narzędzi C ++ z zestawu narzędzi i grozi złamaniem ich kodu.Generalnie, jeśli używasz
auto
, powinno to być spowodowane tym, że twój kod działa dla każdego rozsądnego typu , a nie dlatego, że jesteś po prostu zbyt leniwy, aby zapisać typ, z którym powinien działać. Jeśli używasz goauto
jako narzędzia pomagającego lenistwu, to w końcu zaczniesz wprowadzać do programu subtelne błędy , zwykle spowodowane niejawnymi konwersjami, które nie miały miejsca, ponieważ użyłeśauto
.Niestety, te błędy są trudne do zilustrowania w krótkim przykładzie tutaj, ponieważ ich zwięzłość czyni je mniej przekonującymi niż rzeczywiste przykłady, które pojawiają się w projekcie użytkownika - jednak łatwo pojawiają się w kodzie z dużą ilością szablonów, który wymaga pewnych niejawnych konwersji miejsce.
Jeśli chcesz mieć przykład, jest tutaj . Jednak mała uwaga: zanim skusisz się, by skoczyć i skrytykować kod: pamiętaj, że wiele znanych i dojrzałych bibliotek zostało opracowanych wokół takich niejawnych konwersji i są one tam, ponieważ rozwiązują problemy, które mogą być trudne, jeśli nie niemożliwe rozwiązać inaczej. Spróbuj znaleźć lepsze rozwiązanie, zanim je skrytykujesz.
źródło
which was great because typedef allowed the designer of the library to help you figure out what the return type should be, so that their library works as expected. When you use auto, you take away that control from the class's designer and instead ask the compiler to figure out what the type should be
IMO to nie jest dobry powód. Aktualne środowiska IDE, na przykład Visual Studio 2015, umożliwiają sprawdzenie typu zmiennej przez najechanie kursoremauto
. To jest * dokładnie * to samo co tentypedef
.typename std::iterator_traits<It>::value_type
. (2) Chodziło o to, że wywnioskowany typ nie musi być „dokładnie taki sam”, jak właściwy typ zamierzony przez poprzedniego projektanta kodu; używającauto
, odbierasz projektantowi możliwość określenia prawidłowego typu.vector<bool>
bzdury" ... przepraszam? Jak myślisz, jakbitset
jest realizowane? A może uważasz, że pojemniki na bity są całkowicie nonsensem ?!auto
nie ma wady per se , i opowiadają się (ręcznie wavily) używać go wszędzie w nowym kodem. Pozwala kodowi na konsekwentne sprawdzanie typu i konsekwentne unikanie cichego cięcia. (JeśliB
pochodzi od,A
a funkcja zwracającaA
nagle zwracaB
, toauto
zachowuje się zgodnie z oczekiwaniami, przechowując swoją wartość zwracaną)Chociaż starszy kod sprzed C ++ 11 może opierać się na niejawnych konwersjach wywołanych użyciem jawnie wpisanych zmiennych. Zmiana jawnie wpisanej zmiennej na
auto
może zmienić zachowanie kodu , więc lepiej zachowaj ostrożność.źródło
auto
ma on wady jako takie (a przynajmniej - wielu uważa, że tak). Rozważmy przykład podany w drugim pytaniu w tej dyskusji panelowej z Sutter, Alexandrescu i Meyers: Jeśli maszauto x = foo(); if (x) { bar(); } else { baz(); }
ifoo()
zwracabool
- co się stanie, jeślifoo()
zmiany zwracają wyliczenie (trzy opcje zamiast dwóch)?auto
Kod będzie nadal działać, ale nieoczekiwane rezultaty.bool
zamiastauto
zmiany cokolwiek w przypadku wyliczenia bez zakresu? Mogę się mylić (nie mogę tego sprawdzić tutaj), ale myślę, że jedyną różnicą jest to, że konwersja dobool
odbywa się przy deklaracji zmiennej zamiast przy ocenie warunku wif
. Jeślienum
ma zakres, konwersja dobool
nie nastąpi bez wyraźnego powiadomienia.Słowo kluczowe
auto
po prostu wywnioskuje typ z wartości zwracanej. Dlatego nie jest odpowiednikiem obiektu Pythona, npPonieważ
auto
jest wywnioskowany podczas kompilacji, nie będzie miał żadnych wad w czasie wykonywania.źródło
type()
w Pythonie. Wyprowadza typ, ale nie tworzy nowej zmiennej tego typu.decltype
.auto
służy specjalnie do przypisywania zmiennych.To, o czym nikt tu dotąd nie wspomniał, ale samo w sobie jest warte odpowiedzi, gdybyś mnie zapytał.
Ponieważ (nawet jeśli wszyscy powinni być tego świadomi
C != C++
) kod napisany w C można łatwo zaprojektować, aby zapewnić podstawę dla kodu C ++, a zatem być zaprojektowany bez zbytniego wysiłku, aby był zgodny z C ++, może to być wymaganie przy projektowaniu.Wiem o pewnych regułach, w których niektóre dobrze zdefiniowane konstrukcje
C
są nieważneC++
i odwrotnie. Ale to po prostu spowodowałoby uszkodzenie plików wykonywalnych i zastosowanie ma znana klauzula UB, która w większości przypadków jest zauważana przez dziwne pętle powodujące awarie lub cokolwiek innego (lub nawet może pozostać niewykryte, ale to nie ma znaczenia tutaj).Ale
auto
po raz pierwszy 1 to zmiany!Wyobraź sobie, że wcześniej użyłeś
auto
jako specyfikatora klasy pamięci i prześlij kod. Nie musiałoby to nawet (w zależności od sposobu użycia) „złamać się”; faktycznie może po cichu zmienić zachowanie programu.Należy o tym pamiętać.
1 Przynajmniej za pierwszym razem, kiedy jestem tego świadomy.
źródło
int
” w C, zasługujesz na wszystkie złe rzeczy, które z tego wynikną. A jeśli nie polegasz na tym, użycieauto
jako specyfikatora klasy pamięci obok typu da ci niezły błąd kompilacji w C ++ (co w tym przypadku jest dobrą rzeczą).Jednym z powodów, które przychodzą mi do głowy, jest to, że tracisz możliwość przymuszenia klasy, która jest zwracana. Jeśli twoja funkcja lub metoda zwróciła długi 64-bitowy, a chciałeś tylko 32 bez znaku int, tracisz możliwość kontrolowania tego.
źródło
Jak opisałem w tej odpowiedzi,
auto
czasami mogą wystąpić dziwne sytuacje, których nie zamierzałeś. Musisz wprost powiedzieć,auto&
że masz typ referencyjny, a robiąc to, po prostuauto
możesz stworzyć typ wskaźnika. Może to spowodować zamieszanie, jeśli pominie się specyfikator razem, co spowoduje kopię odwołania zamiast rzeczywistego odniesienia.źródło
auto
jest, nigdy nie wnioskując o referencji aniconst
typie. W celachauto
informacyjnych lepiej użyjauto&&
. (odniesienie uniwersalne) Jeśli typ nie jest tani do skopiowania lub posiada zasób, to nie powinien być kopiowalny.Kolejny irytujący przykład:
generuje ostrzeżenie (
comparison between signed and unsigned integer expressions [-Wsign-compare]
), ponieważi
jest podpisaną int. Aby tego uniknąć, musisz napisać npa może lepiej:
źródło
size
zwrotysize_t
, musiałbyś miećsize_t
dosłowne polubienie0z
. Ale możesz zadeklarować UDL, aby to zrobić. (size_t operator""_z(...)
)unsigned
prawdopodobnie nie jest wystarczająco duży, aby pomieścić wszystkie wartościstd::size_t
w głównych architekturach, więc w mało prawdopodobnym przypadku, gdy ktoś miał pojemnik z absurdalnie gigantyczną liczbą elementów, użycieunsigned
może spowodować nieskończoną pętlę w niższym zakresie indeksów. Chociaż jest to mało prawdopodobne,std::size_t
powinno być używane do uzyskania czystego kodu, który odpowiednio sygnalizuje zamiar. Nie jestem pewien, czy nawetunsigned long long
jest ściśle zagwarantowane, że wystarczy, chociaż w praktyce prawdopodobnie musi być to samo.unsigned long long
gwarantowane jest co najmniej 64 bity, alesize_t
przypuszczam, że teoretycznie może być większe niż to. Oczywiście, jeśli masz> 2 ^ 64 elementów w swoim kontenerze, możesz mieć większe problemy do zmartwień ... ;-)Myślę, że
auto
jest dobre, gdy jest używane w zlokalizowanym kontekście, w którym czytelnik łatwo i oczywiście może wywnioskować jego typ, lub dobrze udokumentowane komentarzem tego typu lub nazwą, która określa rzeczywisty typ. Ci, którzy nie rozumieją, jak to działa, mogą przyjąć to w niewłaściwy sposób, na przykład używając go zamiasttemplate
lub podobnie. Moim zdaniem, oto kilka dobrych i złych przypadków użycia.Dobre zastosowania
Iteratory
Wskaźniki funkcji
Złe zastosowania
Przepływ danych
Podpis funkcji
Przypadki trywialne
źródło
int
, musisz wpisać jeszcze jeden znak, jeśli chceszauto
. To nie do przyjęciaint
równie dobrze widoczne tutaj, a pisanieint
jest krótsze. Dlatego jest to trywialny przypadek.Dziwię się, że nikt o tym nie wspomniał, ale przypuśćmy, że obliczasz silnię czegoś:
Ten kod wyświetli to:
To zdecydowanie nie był oczekiwany rezultat. Stało się tak, ponieważ
auto
wydedukowałem typ zmiennej silnia,int
ponieważ została do niej przypisana1
.źródło