Wiem, że at()
jest to wolniejsze niż []
ze względu na sprawdzanie granic, które jest również omawiane w podobnych pytaniach, takich jak C ++ Vector at / [] operator speed lub :: std :: vector :: at () vs operator [] << zaskakujące wyniki !! 5 do 10 razy wolniej / szybciej! . Po prostu nie rozumiem, do czego ta at()
metoda jest dobra.
Jeśli mam prosty wektor taki jak ten: std::vector<int> v(10);
i decyduję się uzyskać dostęp do jego elementów za pomocą at()
zamiast []
w sytuacji, gdy mam indeks i
i nie jestem pewien, czy jest w granicach wektorów, zmusza mnie to do owinięcia go try-catch blok :
try
{
v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
...
}
chociaż jestem w stanie uzyskać to samo zachowanie, używając size()
i sprawdzając indeks samodzielnie, co wydaje mi się łatwiejsze i znacznie wygodniejsze:
if (i < v.size())
v[i] = 2;
Więc moje pytanie brzmi:
jakie są zalety używania vector :: at over vector :: operator [] ?
Kiedy powinienem używać vector :: at zamiast vector :: size + vector :: operator [] ?
if (i < v.size()) v[i] = 2;
istnieje możliwa ścieżka kodu, która w ogóle nie jest przypisana2
do żadnego elementuv
. Jeśli to właściwe zachowanie, świetnie. Ale często nie ma nic sensownego, że ta funkcja może zrobić, kiedyi >= v.size()
. Nie ma więc szczególnego powodu, dla którego nie powinien używać wyjątku do wskazania nieoczekiwanej sytuacji. Wiele funkcji po prostu używaoperator[]
bez sprawdzania rozmiaru, dokumentu, któryi
musi znajdować się w zasięgu, i obwinia wywołującego o wynikowy UB.Odpowiedzi:
Powiedziałbym, że wyjątki, które
vector::at()
rzucają, nie są tak naprawdę przeznaczone do przechwytywania przez bezpośrednio otaczający kod. Są one głównie przydatne do wyłapywania błędów w kodzie. Jeśli potrzebujesz sprawdzić ograniczenia w czasie wykonywania, ponieważ np. Indeks pochodzi z danych wejściowych użytkownika, rzeczywiście najlepiej będzie, jeśli skorzystasz zif
instrukcji. Podsumowując, zaprojektuj swój kod z zamiarem,vector::at()
który nigdy nie zgłosi wyjątku, więc jeśli tak się stanie, a program zostanie przerwany, jest to oznaka błędu. (tak jakassert()
)źródło
size()
+,[]
gdy indeks zależy od danych wejściowych użytkownika, używaj goassert
w sytuacjach, w których indeks nigdy nie powinien znajdować się poza zakresem, aby łatwo naprawić błędy w przyszłości i.at()
we wszystkich innych sytuacjach (na wszelki wypadek, ponieważ może się zdarzyć coś złego. .)vector
, prawdopodobnie lepiej jest użyć jej jako opcji „na wszelki wypadek” niżat()
wszędzie. W ten sposób możesz liczyć na nieco lepszą wydajność w trybie wydania, na wypadek, gdybyś kiedykolwiek tego potrzebował.operator[]
, np. Gcc.gnu.org/onlinedocs/libstdc++/manual/, więc jeśli Twoja platforma to obsługuje, prawdopodobnie najlepiej będzie z tego korzystać!Nie, nie (blok try / catch może znajdować się powyżej). Jest to przydatne, gdy chcesz, aby wyjątek został wyrzucony, a nie program, aby wejść w niezdefiniowaną dziedzinę zachowania.
Zgadzam się, że większość dostępów poza granicami do wektorów jest błędem programisty (w takim przypadku powinieneś użyć
assert
do łatwiejszego zlokalizowania tych błędów; większość wersji bibliotek standardowych do debugowania robi to automatycznie). Nie chcesz używać wyjątków, które można połknąć w celu zgłaszania błędów programisty: chcesz mieć możliwość naprawienia błędu .Ponieważ jest mało prawdopodobne, aby dostęp do wektora poza granicami był częścią normalnego przebiegu programu (w takim przypadku masz rację: sprawdź wcześniej za pomocą
size
zamiast pozwolić, aby wyjątek się pojawił), zgadzam się z twoją diagnostyką:at
jest zasadniczo bezużyteczna.źródło
out_of_range
wyjątku,abort()
zostanie wywołany.try..catch
może występować w metodzie, która wywołuje tę metodę.at
jest przydatne w takim stopniu, w jakim w innym przypadku piszesz coś takiegoif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
. Ludzie często myślą o funkcjach wyrzucających wyjątki w kategoriach „przekleństwa, muszę obsłużyć wyjątek”, ale tak długo, jak dokładnie dokumentujesz, co może rzucać każda z funkcji, można ich również używać jako „super, nie mam trzeba sprawdzić warunek i zgłosić wyjątek ”.out_of_range
pochodzi odlogic_error
, a inni programiści „powinni” wiedzieć lepiej, niż wychwytywać programylogic_error
i ignorować je.assert
można też zignorować, jeśli Twoi koledzy nie chcą wiedzieć o swoich błędach, jest to po prostu trudniejsze, ponieważ muszą kompilować Twój kodNDEBUG
;-) Każdy mechanizm ma swoje zalety i wady.Ważną kwestią jest tutaj to, że wyjątki pozwalają na oddzielenie normalnego przepływu kodu od logiki obsługi błędów, a pojedynczy blok catch może obsługiwać problemy generowane przez niezliczone miejsca przesyłania, nawet jeśli są rozproszone głęboko w wywołaniach funkcji. Tak więc
at()
niekoniecznie jest to łatwiejsze do pojedynczego użycia, ale czasami staje się łatwiejsze - i mniej zaciemniające logikę normalnych przypadków - gdy masz dużo indeksowania do sprawdzenia.Warto również zauważyć, że w niektórych typach kodu indeks jest zwiększany w złożony sposób i stale używany do wyszukiwania tablicy. W takich przypadkach o wiele łatwiej jest zapewnić prawidłowe sprawdzenia za pomocą
at()
.Jako przykład ze świata rzeczywistego mam kod, który tokenizuje C ++ na elementy leksykalne, a następnie inny kod, który przesuwa indeks nad wektorem tokenów. W zależności od tego, co napotkano, mogę chcieć zwiększyć i sprawdzić następny element, na przykład:
if (token.at(i) == Token::Keyword_Enum) { ASSERT_EQ(tokens.at(++i), Token::Idn); if (tokens.at(++i) == Left_Brace) ... or whatever
W takiej sytuacji bardzo trudno jest sprawdzić, czy nieprawidłowo dotarłeś do końca danych wejściowych, ponieważ jest to bardzo zależne od dokładnych napotkanych tokenów. Jawne sprawdzanie w każdym punkcie użycia jest bolesne i jest znacznie więcej miejsca na błędy programisty, gdy wkraczają inkrementacje przed / po, przesunięcia w miejscu użycia, błędne rozumowanie o ciągłej ważności niektórych wcześniejszych testów itp.
źródło
at
może być wyraźniejsze, jeśli masz wskaźnik do wektora:return pVector->at(n); return (*pVector)[n]; return pVector->operator[](n);
Pomijając wydajność, pierwszym z nich jest prostszy i jaśniejszy kod.
źródło
Po pierwsze, czy
at()
czyoperator[]
jest wolniejsze nie jest określona. Gdy nie ma błędu związanego z ograniczeniami, spodziewałbym się, że będą miały mniej więcej taką samą prędkość, przynajmniej w kompilacjach debugowania. Różnica polega na tym, żeat()
dokładnie określa, co się stanie, gdy wystąpi błąd granic (wyjątek), gdzie tak jak w przypadkuoperator[]
, jest to niezdefiniowane zachowanie - awaria we wszystkich systemach, których używam (g ++ i VC ++), przynajmniej wtedy, gdy używane są zwykłe flagi debugowania. (Inną różnicą jest to, że gdy jestem pewien swojego kodu, mogę uzyskać znaczny wzrost szybkościoperator[]
, wyłączając debugowanie. Jeśli wydajność tego wymaga - nie zrobiłbym tego, gdyby nie było to konieczne.)W praktyce
at()
rzadko jest odpowiednie. Jeśli kontekst jest taki, że wiesz, że indeks może być nieprawidłowy, prawdopodobnie chcesz jawnego testu (np. Aby zwrócił wartość domyślną lub coś w tym rodzaju), a jeśli wiesz, że nie może być nieprawidłowy, chcesz przerwać (i jeśli nie wiesz, czy może być nieprawidłowy, czy nie, sugerowałbym dokładniejsze określenie interfejsu funkcji). Jest jednak kilka wyjątków, w których nieprawidłowy indeks może wynikać z analizy danych użytkownika, a błąd powinien spowodować przerwanie całego żądania (ale nie spowoduje wyłączenia serwera); w takich przypadkach wyjątek jest odpowiedni iat()
zrobi to za Ciebie.źródło
operator[]
nie są zmuszeni do sprawdzania granicy , a takat()
jest? Czy przez to sugerujesz problemy z buforowaniem, spekulacjami i rozgałęzianiem buforów?at()
.std::string
nie zawsze działał, jeśli opcje sprawdzania nie odpowiadały opcjom środowiska wykonawczego:-MD
i lepiej wyłącz sprawdzanie-MDd
, i lepiej dalej.)operator[]
zostać uszkodzony? Npstd::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y)...
. Myślę, że wymuszanie sprawdzania granic dostarczonych plików binarnych podlega przedwczesnej pesymizacji. Imho, powinno to być tylko pomoc dla zespołu dla niezbyt dobrze zaprojektowanego kodu.Cały sens używania wyjątków polega na tym, że kod obsługi błędów może być dalej.
W tym konkretnym przypadku dane wprowadzane przez użytkownika są rzeczywiście dobrym przykładem. Wyobraź sobie, że chcesz semantycznie przeanalizować strukturę danych XML, która używa indeksów do odwoływania się do jakiegoś rodzaju zasobu, który przechowujesz wewnętrznie w pliku
std::vector
. Teraz drzewo XML jest drzewem, więc prawdopodobnie chcesz użyć rekurencji do jego analizy. W głębi, w rekurencji, może wystąpić naruszenie zasad dostępu ze strony autora pliku XML. W takim przypadku zazwyczaj chcesz usunąć wszystkie poziomy rekurencji i po prostu odrzucić cały plik (lub jakąkolwiek „grubszą” strukturę). Tutaj przydaje się at. Możesz po prostu napisać kod analizy tak, jakby plik był prawidłowy. Kod biblioteki zajmie się wykrywaniem błędów i możesz po prostu wyłapać błąd na poziomie zgrubnym.Również inne kontenery, takie jak
std::map
, również mająstd::map::at
nieco inną semantykę niżstd::map::operator[]
: at mogą być używane na mapie const, podczas gdyoperator[]
nie mogą. Teraz, jeśli chcesz napisać kod niezależny od kontenera, na przykład coś, co mogłoby sobie poradzić z jednymconst std::vector<T>&
lub drugimconst std::map<std::size_t, T>&
,ContainerType::at
byłoby twoją bronią z wyboru.Jednak wszystkie te przypadki pojawiają się zwykle podczas obsługi jakiegoś rodzaju niezatwierdzonych danych wejściowych. Jeśli jesteś pewien swojego prawidłowego zakresu, jak zwykle powinieneś, możesz zwykle użyć
operator[]
, ale jeszcze lepiej, iteratorów zbegin()
iend()
.źródło
Zgodnie z tym artykułem, pomijając wydajność, nie ma znaczenia użycie
at
luboperator[]
tylko wtedy, gdy gwarantuje się, że dostęp będzie mieścił się w rozmiarze wektora. W przeciwnym razie, jeśli dostęp jest oparty tylko na pojemności wektora, bezpieczniej jest z niego korzystaćat
.źródło
Uwaga: wygląda na to, że niektórzy nowi ludzie odrzucają tę odpowiedź bez uprzejmości, by powiedzieć, co jest nie tak. Poniższa odpowiedź jest poprawna i można ją tutaj zweryfikować .
Tak naprawdę jest tylko jedna różnica:
at
sprawdza granice, aoperator[]
nie. Dotyczy to zarówno kompilacji debugowania, jak i kompilacji wydań i jest to bardzo dobrze określone przez standardy. To takie proste.Jest
at
to wolniejsza metoda, ale bardzo zła rada, aby jej nie używaćat
. Musisz patrzeć na liczby bezwzględne, a nie względne. Mogę się bezpiecznie założyć, że większość twojego kodu wykonuje grubo droższe operacje niżat
. Osobiście staram się używać,at
ponieważ nie chcę, aby paskudny błąd powodował niezdefiniowane zachowanie i przedostał się do produkcji.źródło
std::out_of_range
lub jakakolwiek formastd::logic_error
jest w rzeczywistości, błąd logiczny w sobie tutaj .at
i,[]
a moja odpowiedź po prostu określa różnicę. Osobiście używam „bezpiecznej” metody, gdy perfekcyjny nie jest problemem. Jak mówi Knuth, nie rób przedwczesnej optymalizacji. Poza tym, bez względu na różnice filozoficzne, dobrze jest złapać błędy wcześniej niż w produkcji.