Ich weiß, dass dies at()
langsamer ist als []
aufgrund seiner Grenzprüfung, die auch in ähnlichen Fragen wie C ++ Vector bei / [] Operatorgeschwindigkeit oder :: std :: vector :: at () vs Operator [] << überraschende Ergebnisse diskutiert wird !! 5 bis 10 mal langsamer / schneller! . Ich verstehe einfach nicht, wozu die at()
Methode gut ist.
Wenn ich einen einfachen Vektor wie diesen habe: std::vector<int> v(10);
und ich beschließe, auf seine Elemente zuzugreifen , indem ich ihn at()
anstelle von []
in Situationen verwende, in denen ich einen Index habe, i
und ich nicht sicher bin, ob er in Vektoren begrenzt ist, zwingt er mich, ihn mit try-catch zu umschließen Block :
try
{
v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
...
}
Obwohl ich in der Lage bin, das gleiche Verhalten zu erzielen, size()
indem ich den Index selbst verwende und überprüfe, was für mich einfacher und bequemer erscheint:
if (i < v.size())
v[i] = 2;
Meine Frage lautet also:
Welche Vorteile bietet die Verwendung von vector :: at gegenüber vector :: operator [] ?
Wann sollte ich vector :: at anstelle von vector :: size + vector :: operator [] verwenden ?
if (i < v.size()) v[i] = 2;
einen möglichen Codepfad gibt,2
der keinem Element vonv
überhaupt zugewiesen wird . Wenn das das richtige Verhalten ist, großartig. Aber oft gibt es nichts Sinnvolles, was diese Funktion tun kann, wenni >= v.size()
. Es gibt also keinen besonderen Grund, warum keine Ausnahme verwendet werden sollte, um auf eine unerwartete Situation hinzuweisen. Viele Funktionen werden nuroperator[]
ohne Überprüfung der Größe und des Dokuments verwendet,i
das sich in Reichweite befinden muss, und geben dem Anrufer die Schuld an der resultierenden UB.Antworten:
Ich würde sagen, dass die Ausnahmen, die
vector::at()
Würfe auslösen, nicht wirklich dazu gedacht sind, vom unmittelbar umgebenden Code erfasst zu werden. Sie sind hauptsächlich nützlich, um Fehler in Ihrem Code zu erkennen. Wenn Sie zur Laufzeit die Grenzen überprüfen müssen, weil der Index beispielsweise aus Benutzereingaben stammt, sollten Sie in der Tat eineif
Anweisung verwenden. Entwerfen Sie Ihren Code zusammenfassend mit der Absicht, dassvector::at()
niemals eine Ausnahme ausgelöst wird. Wenn dies der Fall ist und Ihr Programm abgebrochen wird, ist dies ein Zeichen für einen Fehler. (genau wie einassert()
)quelle
size()
+ verwenden,[]
wenn der Index von Benutzereingaben abhängt. Verwenden Sie ihnassert
in Situationen , in denen der Index für eine einfache Fehlerbehebung in der Zukunft und.at()
in allen anderen Situationen niemals außerhalb der Grenzen liegen sollte (nur für den Fall, dass etwas falsch passieren könnte). .)vector
ist es wahrscheinlich besser, diese als "Nur für den Fall" -Option zu verwenden, alsat()
überall. Auf diese Weise können Sie auf etwas mehr Leistung im Release-Modus hoffen, falls Sie diese jemals benötigen.operator[]
, z. B. gcc.gnu.org/onlinedocs/libstdc++/manual/…. Wenn Ihre Plattform dies unterstützt, sollten Sie dies wahrscheinlich am besten tun!Nein, das tut es nicht (der Try / Catch-Block kann vorgelagert sein). Es ist nützlich, wenn Sie möchten, dass eine Ausnahme ausgelöst wird, anstatt dass Ihr Programm in den undefinierten Verhaltensbereich wechselt.
Ich bin damit einverstanden, dass die meisten uneingeschränkten Zugriffe auf Vektoren ein Fehler des Programmierers sind (in diesem Fall sollten Sie
assert
diese Fehler leichter lokalisieren; die meisten Debug-Versionen von Standardbibliotheken tun dies automatisch für Sie). Sie möchten keine Ausnahmen verwenden, die vorgelagert verschluckt werden können, um Programmiererfehler zu melden: Sie möchten in der Lage sein, den Fehler zu beheben .Da es unwahrscheinlich ist, dass ein Zugriff außerhalb der Grenzen auf einen Vektor Teil des normalen Programmablaufs ist (in diesem Fall haben Sie Recht: Überprüfen Sie dies vorher mit,
size
anstatt die Ausnahme in die Luft sprudeln zu lassen), stimme ich Ihrer Diagnose zu:at
ist im Wesentlichen nutzlos.quelle
out_of_range
Ausnahme fange ,abort()
wird aufgerufen.try..catch
kann in der Methode vorhanden sein, die diese Methode aufruft.at
ist es insofern nützlich, als Sie sonst so etwas wie schreiben würdenif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
. Die Leute denken oft an Funktionen, die Ausnahmen auslösen, als "Flüche, ich muss mit der Ausnahme umgehen", aber solange Sie sorgfältig dokumentieren, was jede Ihrer Funktionen auslösen kann, können sie auch als "großartig, ich nicht" verwendet werden müssen eine Bedingung überprüfen und eine Ausnahme auslösen ".out_of_range
ergibt sich auslogic_error
, und andere Programmierer „sollte“ wissen besser als zu fangenlogic_error
s Upstream und sie ignorieren.assert
kann auch ignoriert werden, wenn Ihre Kollegen nicht über ihre Fehler Bescheid wissen möchten, es ist nur schwieriger, weil sie Ihren Code mit kompilieren müssenNDEBUG
;-) Jeder Mechanismus hat seine Vor- und Nachteile.Der wichtige Punkt hierbei ist, dass Ausnahmen die Trennung des normalen Codeflusses von der Fehlerbehandlungslogik ermöglichen und ein einzelner Catch-Block Probleme behandeln kann, die von einer der unzähligen Wurfstellen generiert werden, selbst wenn sie tief in Funktionsaufrufen verstreut sind. Es ist also nicht
at()
unbedingt einfacher für eine einzelne Verwendung, aber manchmal wird es einfacher - und weniger verschleiert die Normalfalllogik -, wenn Sie viel Indizierung zu validieren haben.Es ist auch bemerkenswert, dass in einigen Codetypen ein Index auf komplexe Weise inkrementiert und kontinuierlich zum Nachschlagen eines Arrays verwendet wird. In solchen Fällen ist es viel einfacher, korrekte Überprüfungen mit sicherzustellen
at()
.Als Beispiel aus der Praxis habe ich Code, der C ++ in lexikalische Elemente umwandelt, und dann anderen Code, der einen Index über den Tokenvektor verschiebt. Je nachdem, was angetroffen wird, möchte ich möglicherweise das nächste Element erhöhen und überprüfen, wie in:
In solchen Situationen ist es sehr schwierig zu überprüfen, ob Sie das Ende der Eingabe unangemessen erreicht haben, da dies sehr stark von den genauen gefundenen Token abhängt. Die explizite Überprüfung an jedem Verwendungspunkt ist schmerzhaft, und es gibt viel mehr Raum für Programmiererfehler, da Inkremente vor / nach dem Vorgang, Offsets am Verwendungsort, fehlerhafte Überlegungen zur fortgesetzten Gültigkeit einiger früherer Tests usw. auftreten.
quelle
at
kann klarer sein, wenn Sie einen Zeiger auf den Vektor haben:Abgesehen von der Leistung ist der erste der einfachere und klarere Code.
quelle
Zunächst wird nicht angegeben , ob es langsamer ist
at()
oderoperator[]
nicht. Wenn es keinen Grenzfehler gibt, würde ich erwarten, dass sie ungefähr die gleiche Geschwindigkeit haben, zumindest beim Debuggen von Builds. Der Unterschied besteht darin, dassat()
genau angegeben wird, was passieren wird, wenn ein Grenzfehler vorliegt (eine Ausnahme), bei dem es sich wie im Fall vonoperator[]
um ein undefiniertes Verhalten handelt - ein Absturz in allen von mir verwendeten Systemen (g ++ und VC ++), zumindest wenn Es werden die normalen Debugging-Flags verwendet. (Ein weiterer Unterschied besteht darin, dass ich, sobald ich mir meines Codes sicher bin,operator[]
durch Deaktivieren des Debuggens eine erhebliche Geschwindigkeitssteigerung erzielen kann. Wenn die Leistung dies erfordert, würde ich dies nur tun, wenn dies erforderlich wäre.)In der Praxis
at()
ist selten angemessen. Wenn der Kontext so ist, dass Sie wissen, dass der Index möglicherweise ungültig ist, möchten Sie wahrscheinlich den expliziten Test (z. B. um einen Standardwert oder etwas zurückzugeben), und wenn Sie wissen, dass er nicht ungültig sein kann, möchten Sie abbrechen (und wenn Sie wissen nicht, ob es ungültig sein kann oder nicht. Ich würde vorschlagen, dass Sie die Benutzeroberfläche Ihrer Funktion genauer angeben. Es gibt jedoch einige Ausnahmen, in denen der ungültige Index aus dem Parsen von Benutzerdaten resultieren kann und der Fehler einen Abbruch der gesamten Anforderung verursachen sollte (aber den Server nicht herunterfahren sollte). In solchen Fällen ist eine Ausnahme angebracht,at()
die dies für Sie erledigt.quelle
operator[]
nicht gezwungen sind, ihre Grenzen zu überprüfen, wohingegen dies der Fallat()
ist? Bedeuten Sie damit Caching-, Spekulations- und Verzweigungspufferprobleme?operator[]
ist nicht erforderlich, um Grenzen zu überprüfen, aber alle guten Implementierungen tun dies. Zumindest im Debugging-Modus. Der einzige Unterschied besteht darin, was sie tun, wenn der Index außerhalb der Grenzen liegt:operator[]
Abbruch mit einer Fehlermeldung,at()
Auslösung einer Ausnahme.at()
.std::string
es nicht immer funktionierte, wenn die Überprüfungsoptionen nicht mit denen der Laufzeit übereinstimmten :-MD
, und Sie sollten die Überprüfung deaktivieren-MDd
, und Sie sollten es besser haben es auf.)Der springende Punkt bei der Verwendung von Ausnahmen ist, dass Ihr Fehlerbehandlungscode weiter entfernt sein kann.
In diesem speziellen Fall ist die Benutzereingabe in der Tat ein gutes Beispiel. Stellen Sie sich vor, Sie möchten eine XML-Datenstruktur semantisch analysieren, die Indizes verwendet, um auf eine Ressource zu verweisen, die Sie intern in einer speichern
std::vector
. Jetzt ist der XML-Baum ein Baum, daher möchten Sie ihn wahrscheinlich mithilfe der Rekursion analysieren. Tief im Inneren der Rekursion kann es zu einer Zugriffsverletzung durch den Verfasser der XML-Datei kommen. In diesem Fall möchten Sie normalerweise alle Rekursionsebenen verlassen und einfach die gesamte Datei (oder eine beliebige "gröbere" Struktur) ablehnen. Hier bietet sich at an. Sie können den Analysecode einfach so schreiben, als ob die Datei gültig wäre. Der Bibliothekscode kümmert sich um die Fehlererkennung, und Sie können den Fehler nur auf der Grobebene abfangen.Auch andere Container wie
std::map
habenstd::map::at
eine etwas andere Semantik alsstd::map::operator[]
: at kann auf einer const-Map verwendet werden, währendoperator[]
dies nicht möglich ist. Wenn Sie nun Container-Agnostiker-Code schreiben möchten , wie etwas, das entweder mitconst std::vector<T>&
oder umgehen könnteconst std::map<std::size_t, T>&
,ContainerType::at
wäre dies Ihre bevorzugte Waffe.Alle diese Fälle treten jedoch normalerweise auf, wenn eine nicht validierte Dateneingabe verarbeitet wird. Wenn Sie sich über Ihren gültigen Bereich sicher sind, wie Sie es normalerweise sollten, können Sie normalerweise
operator[]
, aber noch besser, Iteratoren mitbegin()
und verwendenend()
.quelle
Abgesehen von dieser Leistung macht die Verwendung des Artikels keinen Unterschied,
at
oderoperator[]
nur, wenn der Zugriff garantiert innerhalb der Größe des Vektors liegt. Andernfalls ist die Verwendung sicherer, wenn der Zugriff nur auf der Kapazität des Vektors basiertat
.quelle
Hinweis: Es scheint, dass einige neue Leute diese Antwort ablehnen, ohne höflich zu sagen, was falsch ist. Die folgende Antwort ist korrekt und kann hier überprüft werden .
Es gibt wirklich nur einen Unterschied:
at
Überprüft Grenzen, währendoperator[]
dies nicht der Fall ist. Dies gilt sowohl für Debug-Builds als auch für Release-Builds. Dies ist in den Standards sehr gut festgelegt. So einfach ist das.Dies macht
at
eine langsamere Methode, aber es ist auch ein wirklich schlechter Rat, sie nicht zu verwendenat
. Sie müssen sich absolute Zahlen ansehen, keine relativen Zahlen. Ich kann mit Sicherheit wetten, dass der größte Teil Ihres Codes fett teurere Operationen ausführt alsat
. Persönlich versuche ich zu verwenden,at
weil ich nicht möchte, dass ein böser Fehler undefiniertes Verhalten erzeugt und sich in die Produktion einschleicht.quelle
std::out_of_range
oder irgendeine Form vonstd::logic_error
hier tatsächlich ein logischer Fehler an und für sich ist .at
und[]
und meine Antwort gibt einfach den Unterschied an. Ich persönlich verwende die "sichere" Methode, wenn Perf kein Problem ist. Wie Knuth sagt, mache keine vorzeitige Optimierung. Es ist auch gut, Fehler früh als in der Produktion zu erkennen, unabhängig von philosophischen Unterschieden.