vector :: at vs. vector :: operator []

94

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, iund 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 ?

LihO
quelle
11
+1 sehr gute Frage !! aber ich denke nicht, dass bei () das allgemein verwendet wird.
Rohit Vipin Mathews
10
Beachten Sie, dass es in Ihrem Beispielcode if (i < v.size()) v[i] = 2;einen möglichen Codepfad gibt, 2der keinem Element von vüberhaupt zugewiesen wird . Wenn das das richtige Verhalten ist, großartig. Aber oft gibt es nichts Sinnvolles, was diese Funktion tun kann, wenn i >= v.size(). Es gibt also keinen besonderen Grund, warum keine Ausnahme verwendet werden sollte, um auf eine unerwartete Situation hinzuweisen. Viele Funktionen werden nur operator[]ohne Überprüfung der Größe und des Dokuments verwendet, idas sich in Reichweite befinden muss, und geben dem Anrufer die Schuld an der resultierenden UB.
Steve Jessop

Antworten:

74

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 eine ifAnweisung verwenden. Entwerfen Sie Ihren Code zusammenfassend mit der Absicht, dass vector::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 ein assert())

pmdj
quelle
1
+1 Ich mag die Erklärung, wie die Behandlung falscher Benutzereingaben getrennt werden kann (Eingabevalidierung; ungültige Eingabe kann erwartet werden, wird also nicht als etwas Außergewöhnliches angesehen) ... und Fehler im Code (Dereferenzierung des Iterators, der außerhalb des Bereichs liegt, ist außergewöhnlich Sache)
Bojan Komazec
Sie sagen also, ich sollte size()+ verwenden, []wenn der Index von Benutzereingaben abhängt. Verwenden Sie ihn assertin 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). .)
LihO
8
@LihO: Wenn Ihre Implementierung eine Debugging-Implementierung von bietet, vectorist es wahrscheinlich besser, diese als "Nur für den Fall" -Option zu verwenden, als at()überall. Auf diese Weise können Sie auf etwas mehr Leistung im Release-Modus hoffen, falls Sie diese jemals benötigen.
Steve Jessop
3
Ja, die meisten STL-Implementierungen unterstützen heutzutage einen Debug-Modus, der sogar die Grenzen überprüft operator[], z. B. gcc.gnu.org/onlinedocs/libstdc++/manual/…. Wenn Ihre Plattform dies unterstützt, sollten Sie dies wahrscheinlich am besten tun!
PMDJ
1
@pmdj fantastischer Punkt, von dem ich nichts wusste ... aber verwaister Link. : P aktuelle ist: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
underscore_d
16

es zwingt mich, es mit einem Try-Catch-Block zu umwickeln

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 assertdiese 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, sizeanstatt die Ausnahme in die Luft sprudeln zu lassen), stimme ich Ihrer Diagnose zu: atist im Wesentlichen nutzlos.

Alexandre C.
quelle
Wenn ich keine out_of_rangeAusnahme fange , abort()wird aufgerufen.
LihO
@LihO: Nicht unbedingt ... das try..catchkann in der Methode vorhanden sein, die diese Methode aufruft.
Naveen
12
Wenn nichts anderes, atist es insofern nützlich, als Sie sonst so etwas wie schreiben würden if (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 ".
Steve Jessop
@SteveJessop: Ich mag es nicht, Ausnahmen für Programmfehler auszulösen, da sie von anderen Programmierern vorgelagert werden können. Behauptungen sind hier viel nützlicher.
Alexandre C.
6
@AlexandreC. na ja, die offizielle Antwort auf das ist , dass out_of_rangeergibt sich aus logic_error, und andere Programmierer „sollte“ wissen besser als zu fangen logic_errors Upstream und sie ignorieren. assertkann 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üssen NDEBUG;-) Jeder Mechanismus hat seine Vor- und Nachteile.
Steve Jessop
11

Welche Vorteile bietet die Verwendung von vector :: at gegenüber vector :: operator []? Wann sollte ich vector :: at anstelle von vector :: size + vector :: operator [] verwenden?

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:

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

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.

Tony Delroy
quelle
10

at kann klarer sein, wenn Sie einen Zeiger auf den Vektor haben:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

Abgesehen von der Leistung ist der erste der einfachere und klarere Code.

Brangdon
quelle
... besonders wenn Sie einen Zeiger auf das n- te Element eines Vektors benötigen .
Delphin
5

Zunächst wird nicht angegeben , ob es langsamer ist at()oder operator[]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, dass at()genau angegeben wird, was passieren wird, wenn ein Grenzfehler vorliegt (eine Ausnahme), bei dem es sich wie im Fall von operator[]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.

James Kanze
quelle
3
Warum würden Sie erwarten, dass sie ungefähr die gleiche Geschwindigkeit haben, wenn sie operator[]nicht gezwungen sind, ihre Grenzen zu überprüfen, wohingegen dies der Fall at()ist? Bedeuten Sie damit Caching-, Spekulations- und Verzweigungspufferprobleme?
Sebastian Mach
@phresnel 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.
James Kanze
2
Entschuldigung, ich habe Ihr Attribut "Im Debugging-Modus" verpasst. Im Debug-Modus würde ich den Code jedoch nicht an seiner Qualität messen. Im Release-Modus ist eine Überprüfung nur von erforderlich at().
Sebastian Mach
1
@phresnel Der größte Teil des von mir gelieferten Codes befand sich im "Debug" -Modus. Sie deaktivieren die Überprüfung nur, wenn Leistungsprobleme dies tatsächlich erfordern. (Microsoft vor 2010 war hier ein kleines Problem, da std::stringes 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.)
James Kanze
2
Ich bin eher ein Mitglied des Lagers, in dem "Code als standardmäßig sanktioniert (garantiert)" steht. Natürlich können Sie im Debug-Modus liefern, aber wenn Sie plattformübergreifende Entwicklungen durchführen (einschließlich, aber nicht ausschließlich, des gleichen Betriebssystems, aber verschiedener Compiler-Versionen), ist es die beste Wahl für Releases und den Debug-Modus, sich auf den Standard zu verlassen wird als Werkzeug für den Programmierer angesehen, um das Ding meistens richtig und robust zu machen :)
Sebastian Mach
1

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::maphaben std::map::ateine etwas andere Semantik als std::map::operator[]: at kann auf einer const-Map verwendet werden, während operator[]dies nicht möglich ist. Wenn Sie nun Container-Agnostiker-Code schreiben möchten , wie etwas, das entweder mit const std::vector<T>&oder umgehen könnte const std::map<std::size_t, T>&, ContainerType::atwä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 mit begin()und verwenden end().

ltjax
quelle
1

Abgesehen von dieser Leistung macht die Verwendung des Artikels keinen Unterschied, atoder operator[]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 basiert at.

ahj
quelle
1
da draußen sind Drachen. Was passiert, wenn wir auf diesen Link klicken? (Hinweis: Ich weiß es bereits, aber auf StackOverflow bevorzugen wir Kommentare, die nicht unter Link Rot leiden, dh eine kurze Zusammenfassung darüber, was Sie sagen möchten)
Sebastian Mach
Danke für den Tipp. Es ist jetzt behoben.
Ahj
-1

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ährend operator[]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 ateine langsamere Methode, aber es ist auch ein wirklich schlechter Rat, sie nicht zu verwenden at. 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 als at. Persönlich versuche ich zu verwenden, atweil ich nicht möchte, dass ein böser Fehler undefiniertes Verhalten erzeugt und sich in die Produktion einschleicht.

Shital Shah
quelle
1
Ausnahmen in C ++ sollen ein Fehlerbehandlungsmechanismus sein, kein Tool zum Debuggen. Herb Sutter erklärt, warum Werfen std::out_of_rangeoder irgendeine Form von std::logic_errorhier tatsächlich ein logischer Fehler an und für sich ist .
Big Temp
@ BigTemp - Ich bin mir nicht sicher, wie sich Ihr Kommentar auf diese Frage und Antwort bezieht. Ja, Ausnahmen sind hoch diskutierte Themen, aber die Frage hier ist der Unterschied zwischen atund []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.
Shital Shah