Przechodzę w kierunku C ++ 11 z C ++ 98 i zapoznałem się ze auto
słowem kluczowym. Zastanawiałem się, dlaczego musimy jawnie zadeklarować, auto
czy kompilator może automatycznie wydedukować typ. Wiem, że C ++ jest językiem silnie typizowanym i jest to reguła, ale czy nie można było osiągnąć tego samego wyniku bez jawnego zadeklarowania zmiennej auto
?
80
Odpowiedzi:
Porzucenie treści jawnych
auto
złamałoby język:na przykład
int main() { int n; { auto n = 0; // this shadows the outer n. } }
gdzie widać, że upuszczenie
auto
nie zasłoni tego, co zewnętrznen
.źródło
n := 0
wprowadzenie nowej zmiennej. Dlaczegoauto
jest używane to pytanie oparte na opiniach.:=
.). (d) To już pasuje do gramatyki. Myślę, że jest tu bardzo mało miejsca na opinię.x = f()
deklarujesz nową zmienną (jeśli jeszcze nie istnieje), uzyskując typ wartości zwracanej przez f`s ... Wymaganie auto do jawnego zadeklarowania zmiennej zmniejsza jednak ryzyko zadeklarowania nowych zmiennych przez przypadek (np. z powodu literówki ...).Twoje pytanie pozwala na dwie interpretacje:
Batszeba ładnie odpowiedziała na pierwszą interpretację, po drugie rozważ następujące (zakładając, że nie istnieją żadne inne deklaracje; hipotetycznie poprawny C ++):
int f(); double g(); n = f(); // declares a new variable, type is int; d = g(); // another new variable, type is double if(n == d) { n = 7; // reassigns n auto d = 2.0; // new d, shadowing the outer one }
To byłoby możliwe, inne języki uciec całkiem dobrze (no, oprócz kwestii shadowing być może) ... To nie jest tak w C ++, choć i kwestia (w sensie drugiej interpretacji) brzmi: dlaczego ?
Tym razem odpowiedź nie jest tak oczywista, jak w pierwszej interpretacji. Jedno jest jednak oczywiste: wyraźny wymóg słowa kluczowego sprawia, że język jest bezpieczniejszy (nie wiem, czy to właśnie skłoniło komitet językowy do podjęcia decyzji, nadal pozostaje kwestią):
grummel = f(); // ... if(true) { brummel = f(); //^ uh, oh, a typo... }
Czy możemy się z tym zgodzić, nie potrzebując dalszych wyjaśnień?
(cytowany komentarz psmears ze względu na jego znaczenie - dzięki za podpowiedź)
źródło
auto
, moim zdaniem, jest to, że oznacza to, że dodanie zmiennej globalnej w miejscu oddalonym od funkcji (np. W pliku nagłówkowym) może zmienić to, co miało być deklaracją lokalnie zmienna w tej funkcji na przypisanie zmiennej globalnej ... z potencjalnie katastrofalnymi (iz pewnością bardzo zagmatwanymi) konsekwencjami.global <variable>
instrukcji). Oczywiście wymagałoby to jeszcze większej modyfikacji języka C ++, więc prawdopodobnie nie byłoby to wykonalne.MOV R6 R5
SUB #nnn R6
na PDP-11 przy założeniu, że R5 jest używany jako wskaźnik ramki, a R6 jest wskaźnikiem stosu. nnn to liczba potrzebnych bajtów pamięci.Zamierzam nieco przeformułować Twoje pytanie w sposób, który pomoże Ci zrozumieć, dlaczego potrzebujesz
auto
:Czy to nie było możliwe ? Oczywiście było to „możliwe”. Pytanie brzmi, czy warto byłoby to zrobić.
Większość składni w innych językach, które nie używają nazw typów, działa na jeden z dwóch sposobów. Istnieje sposób podobny do Go, w którym
name := value;
deklaruje zmienną. I jest sposób podobny do Pythona, w którymname = value;
deklaruje nową zmienną, jeśliname
nie została wcześniej zadeklarowana.Załóżmy, że nie ma żadnych problemów z składniowe stosowania zarówno składni C ++ (choć już widzę, że
identifier
następuje:
w C ++ oznacza „uczynić etykietę”). Więc co tracisz w porównaniu do symboli zastępczych?Cóż, nie mogę już tego robić:
auto &name = get<0>(some_tuple);
Widzisz,
auto
zawsze oznacza „wartość”. Jeśli chcesz uzyskać odniesienie, musisz jawnie użyć pliku&
. I słusznie zakończy się niepowodzeniem, jeśli wyrażenie przypisania ma wartość prvalue. Żadna ze składni opartych na przypisaniu nie ma sposobu na rozróżnienie między odwołaniami a wartościami.Teraz możesz sprawić, że takie składnie przypisania będą wywnioskować odwołania, jeśli dana wartość jest odwołaniem. Ale to oznaczałoby, że nie możesz:
auto name = get<0>(some_tuple);
To kopiuje z krotki, tworząc obiekt niezależny od
some_tuple
. Czasami właśnie tego chcesz. Jest to jeszcze bardziej przydatne, jeśli chcesz przejść z krotki za pomocąauto name = get<0>(std::move(some_tuple));
.OK, więc może moglibyśmy nieco rozszerzyć te składnie, aby uwzględnić to rozróżnienie. Może
&name := value;
lub&name = value;
chciałoby wydedukować takie odniesienieauto&
.Ok dobrze. A co z tym:
decltype(auto) name = some_thing();
Och, zgadza się; C ++ ma w rzeczywistości dwa symbole zastępcze:
auto
idecltype(auto)
. Podstawową ideą tej dedukcji jest to, że działa dokładnie tak, jakbyś to zrobiłdecltype(expr) name = expr;
. Więc w naszym przypadku, jeślisome_thing()
jest przedmiotem, wydedukuje obiekt. Jeślisome_thing()
jest odniesieniem, wydedukuje odniesienie.Jest to bardzo przydatne, gdy pracujesz w kodzie szablonu i nie masz pewności, jaka dokładnie będzie wartość zwracana funkcji. Świetnie nadaje się do spedycji i jest niezbędnym narzędziem, nawet jeśli nie jest szeroko stosowane.
Więc teraz musimy dodać więcej do naszej składni.
name ::= value;
oznacza „rób to, codecltype(auto)
robi”. Nie mam odpowiednika dla wariantu Pythonic.Patrząc na tę składnię, czy nie jest łatwo przypadkowo błędnie wpisać? Nie tylko to, prawie nie dokumentuje się samemu. Nawet jeśli nigdy wcześniej nie widziałeś
decltype(auto)
, jest to wystarczająco duże i oczywiste, że możesz przynajmniej łatwo stwierdzić, że dzieje się coś wyjątkowego. Natomiast wizualna różnica między::=
i:=
jest minimalna.Ale to jest opinia; są bardziej istotne kwestie. Widzisz, wszystko to opiera się na używaniu składni przypisania. Cóż ... a co z miejscami, w których nie można używać składni przypisań? Lubię to:
for(auto &x : container)
Czy zmienimy to na
for(&x := container)
? Ponieważ wydaje się, że oznacza to coś zupełnie innego niż oparte na zakresiefor
. Wygląda na to, że jest to instrukcja inicjalizująca z regularnejfor
pętli, a nie oparta na zakresiefor
. Byłaby to również inna składnia niż przypadki niewyedukowane.Również inicjalizacja kopiowania (używanie
=
) nie jest tym samym w C ++, co inicjalizacja bezpośrednia (przy użyciu składni konstruktora). Więcname := value;
może nie działać w przypadkach, w którychauto name(value)
by to zrobił.Jasne, możesz zadeklarować, że
:=
użyje bezpośredniej inicjalizacji, ale byłoby to całkiem zgodne ze sposobem, w jaki zachowuje się reszta C ++.Jest jeszcze jedna rzecz: C ++ 14. Dało nam to jedną przydatną funkcję dedukcji: dedukcja typu zwrotu. Ale to jest oparte na symbolach zastępczych. Podobnie jak w przypadku zakresu, opiera
for
się zasadniczo na nazwie typu, która jest wypełniana przez kompilator, a nie na jakiejś składni zastosowanej do określonej nazwy i wyrażenia.Widzisz, wszystkie te problemy pochodzą z tego samego źródła: wymyślasz zupełnie nową składnię do deklarowania zmiennych. Deklaracje oparte na symbolach zastępczych nie musiały wymyślać nowej składni . Używają dokładnie tej samej składni co poprzednio; po prostu używają nowego słowa kluczowego, które działa jak typ, ale ma specjalne znaczenie. To właśnie pozwala mu działać w oparciu o zakres
for
i odliczanie typu zwracanego. To właśnie pozwala mu mieć wiele form (auto
vs.decltype(auto)
). I tak dalej.Symbole zastępcze działają, ponieważ są najprostszym rozwiązaniem problemu, zachowując jednocześnie wszystkie korzyści i ogólność używania rzeczywistej nazwy typu. Jeśli wpadłeś na inną alternatywę, która działała tak uniwersalnie, jak symbole zastępcze, jest bardzo mało prawdopodobne, że byłaby tak prosta, jak symbole zastępcze.
Chyba że była to tylko pisownia symboli zastępczych z różnymi słowami kluczowymi lub symbolami ...
źródło
auto
deklaracje / odliczenie wartości zwracanej.Krótko mówiąc:
auto
w niektórych przypadkach można by odrzucić, ale prowadziłoby to do niespójności.Przede wszystkim, jak wskazano, składnia deklaracji w C ++ to
<type> <varname>
. Jawne deklaracje wymagają w jego miejsce słowa kluczowego typu lub przynajmniej deklaracji. Moglibyśmy więc użyćvar <varname>
lubdeclare <varname>
czegoś podobnego, aleauto
jest to od dawna słowo kluczowe w C ++ i jest dobrym kandydatem na słowo kluczowe automatycznej dedukcji typu.Czy można niejawnie zadeklarować zmienne przez przypisanie bez zrywania wszystkiego?
Czasem tak. Nie możesz wykonać przypisania poza funkcjami, więc możesz użyć składni przypisania dla tamtejszych deklaracji. Ale takie podejście wprowadziłoby niespójność w języku, prawdopodobnie prowadząc do błędów ludzkich.
a = 0; // Error. Could be parsed as auto declaration instead. int main() { return 0; }
A jeśli chodzi o wszelkiego rodzaju zmienne lokalne, jawne deklaracje są sposobem kontrolowania zakresu zmiennej.
a = 1; // use a variable declared before or outside auto b = 2; // declare a variable here
Gdyby dozwolona była niejednoznaczna składnia, deklarowanie zmiennych globalnych mogłoby nagle przekształcić lokalne niejawne deklaracje w przypisania. Znalezienie tych konwersji wymagałoby sprawdzenia wszystkiego . Aby uniknąć kolizji, potrzebowałbyś unikalnych nazw dla wszystkich globali, co w pewnym sensie niweczy całą ideę określania zakresu. Więc jest naprawdę źle.
źródło
auto
jest słowem kluczowym, którego możesz użyć w miejscach, w których zwykle musisz określić typ .int x = some_function();
Można uczynić bardziej ogólnym,
int
automatycznie określając typ:auto x = some_function();
Jest to więc konserwatywne rozszerzenie języka; pasuje do istniejącej składni. Bez niej
x = some_function()
staje się instrukcją przypisania, a nie deklaracją.źródło
składnia musi być jednoznaczna, a także kompatybilna wstecz.
Jeśli porzucisz auto, nie będzie możliwości rozróżnienia między instrukcjami a definicjami.
auto n = 0; // fine n=0; // statememt, n is undefined.
źródło
auto
było to już słowo kluczowe (ale o przestarzałym znaczeniu), więc nie złamało kodu, używając go jako nazwy. Z tego powodu nie wybrano zamiast tego lepszego słowa kluczowego, takiego jakvar
lublet
.auto
jest w rzeczywistości całkiem doskonałym słowem kluczowym do tego celu: wyraża dokładnie to, co oznacza, mianowicie zastępuje nazwę typu słowem „typ automatyczny”. W przypadku słowa kluczowego takiego jakvar
lublet
, należy konsekwentnie wymagać słowa kluczowego, nawet jeśli typ jest określony jawnie, tj.var int n = 0
Lub coś podobnegovar n:Int = 0
. Tak to się w zasadzie robi w Rust.auto
jest zdecydowanie doskonała w kontekście istniejącej składni, powiedziałbym, że coś w rodzajuvar int x = 42
bycia podstawową definicją zmiennej, ze skrótamivar x = 42
iint x = 42
jako skróty, miałoby więcej sensu niż obecna składnia, jeśli rozpatruje się ją poza treścią historyczną. Ale to głównie kwestia gustu. Ale masz rację, powinienem był napisać „jeden z powodów” zamiast „powodu” w moim oryginalnym komentarzu :)auto
, istnieje typ automatyczny (inny, w zależności od wyrażenia).Dodając do poprzednich odpowiedzi jedną dodatkową notatkę ze starego pierdnięcia: Wygląda na to, że możesz uznać to za zaletę, aby móc po prostu zacząć używać nowej zmiennej bez jej deklarowania.
W językach z możliwością niejawnej definicji zmiennych może to stanowić duży problem, szczególnie w większych systemach. Popełniasz jedną literówkę i debugujesz godzinami tylko po to, aby dowiedzieć się, że nieumyślnie wprowadziłeś zmienną o wartości zero (lub gorszej) -
blue
vsbleu
,label
vslable
... w rezultacie nie możesz naprawdę ufać żadnemu kodowi bez dokładnego sprawdzenia dokładności nazwy zmiennych.Samo użycie
auto
mówi zarówno kompilatorowi, jak i opiekunowi, że Twoim zamiarem jest zadeklarowanie nowej zmiennej.Pomyśl o tym, aby móc uniknąć tego rodzaju koszmarów, w FORTRANIE zostało wprowadzone stwierdzenie „implicit none” - i widzisz, że jest ono używane we wszystkich poważnych programach FORTRAN obecnie. Brak tego jest po prostu ... przerażający.
źródło