ChatGPT解决这个技术问题 Extra ChatGPT

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

I know that at() is slower than [] because of its boundary checking, which is also discussed in similar questions like C++ Vector at/[] operator speed or ::std::vector::at() vs operator[] << surprising results!! 5 to 10 times slower/faster!. I just don't understand what the at() method is good for.

If I have a simple vector like this one: std::vector<int> v(10); and I decide to access its elements by using at() instead of [] in situation when I have a index i and I'm not sure if its in vectors bounds, it forces me to wrap it with try-catch block:

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

although I'm able to do the get the same behaviour by using size() and checking the index on my own, which seems easier and much convenient for me:

if (i < v.size())
    v[i] = 2;

So my question is:
What are advantages of using vector::at over vector::operator[] ?
When should I use vector::at rather than vector::size + vector::operator[] ?

+1 very good question!! but i dont think at() is that commonly used.
Note that in your example code, if (i < v.size()) v[i] = 2;, there's a possible code path that doesn't assign 2 to any element of v at all. If that's the correct behavior, great. But often there's nothing sensible that this function can do when i >= v.size(). So there's no particular reason why it shouldn't use an exception to indicate an unexpected situation. Many functions just use operator[] without a check against the size, document that i must be in range, and blame the resulting UB on the caller.
The use of at is safer. For example, given an obj vector with 100 elements. The obj.at(143) = 69; explodes right away. Whereas, obj[143] = 69; will silently creep in without your notice.

p
pmdj

I'd say the exceptions that vector::at() throws aren't really intended to be caught by the immediately surrounding code. They are mainly useful for catching bugs in your code. If you need to bounds-check at runtime because e.g. the index comes from user input, you're indeed best off with an if statement. So in summary, design your code with the intention that vector::at() will never throw an exception, so that if it does, and your program aborts, it's a sign of a bug. (just like an assert())


+1 I like the explanation of how to separate handling of wrong user's input (input validation; invalid input might be expected so is not regarded as something exceptional)...and bugs in the code (dereferencing iterator that is out of range is exceptional thing)
So you say that I should use size() + [] when index depends on users input, use assert in situations where index should never be out of bounds for easy bug fixing in the future and .at() in all other situations (just in case, cause something wrong might happen...)
@LihO: if your implementation offers a debugging implementation of vector then it's probably better to use that as the "just in case" option rather than at() everywhere. That way you can hope for a bit more performance in release mode, just in case you ever need it.
Yeah, most STL implementations these days support a debug mode which bounds-checks even operator[], e.g. gcc.gnu.org/onlinedocs/libstdc++/manual/… so if your platform supports this, you're probably best off going with it!
@pmdj fantastic point, which i didn't know about... but orphaned link. :P current one is: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
A
Alexandre C.

it forces me to wrap it with try-catch block

No it doesn't (the try/catch block can be upstream). It is useful when you want an exception to be thrown rather than your program to enter undefined behavior realm.

I agree that most out of bounds accesses to vectors are a programmer's mistake (in which case you ought to use assert to locate those mistakes more easily; most debug versions of standard libraries do this automatically for you). You do not want to use exceptions that can be swallowed upstream to report programmer mistakes: you want to be able to fix the bug.

Since it is unlikely that an out of bounds access to a vector is part of the normal program flow (in the case it is, you're right: check beforehand with size instead of letting the exception bubble up), I agree with your diagnostic: at is essentially useless.


If I don't catch out_of_range exception, then abort() is called.
@LihO: Not necessarily..the try..catch can be present in the method which is calling this method.
If nothing else, at is useful to the extent that you would otherwise find yourself writing something like if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }. People often think of exception-throwing functions in terms of "curses, I have to handle the exception", but as long as you carefully document what each of your functions can throw, they can also be used as "great, I don't have to check a condition and throw an exception".
@SteveJessop: I don't like throwing exceptions for program bugs, since they can be caught upstream by other programmers. Assertions are much more useful here.
@AlexandreC. well, the official response to that is that out_of_range derives from logic_error, and other programmers "should" know better than to catch logic_errors upstream and ignore them. assert can be ignored too if your colleagues are keen not to know about their mistakes, it's just harder because they have to compile your code with NDEBUG ;-) Each mechanism has its merits and flaws.
T
Tony Delroy

What are advantages of using vector::at over vector::operator[] ? When should I use vector::at rather than vector::size + vector::operator[] ?

The important point here is that exceptions allow separation of the normal flow of code from the error handling logic, and a single catch block can handle problems generated from any of myriad throw sites, even if scattered deep within function calls. So, it's not that at() is necessarily easier for a single use, but that sometimes it becomes easier - and less obfuscating of normal-case logic - when you have a lot of indexing to validate.

It's also noteworthy that in some types of code, an index is being incremented in complex ways, and continually used to look up an array. In such cases, it's much easier to ensure correct checks using at().

As a real-world example, I have code that tokenises C++ into lexical elements, then other code that moves an index over the vector of tokens. Depending on what's encountered, I may wish to increment and check the next element, as in:

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

In this kind of situation, it's very hard to check whether you've inappropriately reached the end of the input because that's very dependent on the exact tokens encountered. Explicit checking at each point of use is painful, and there's much more room for programmer error as pre/post increments, offsets at the point of use, flawed reasoning about the continued validity of some earlier test etc. kick in.


B
Brangdon

at can be clearer if you have a pointer to the vector:

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

Performance aside, the first of these is the simpler and clearer code.


...especially when you need a pointer to the n-th element of a vector.
That's not a good enough reason, IMHO, to prefer at(). Just write: auto& vector = *pVector; and now you can do return vector[n]. Also, you should really avoid working through pointers (as opposed to references) directly, especially to complex classes.
@einpoklum I've had code not pass our university's auto-grader timed tests because of at()... simply replacing all calls with operator[] made the code run quick enough to pass all tests. at() and operator[] have a very noticeable performance difference.
e
einpoklum

In debug builds, it is not guaranteed for at() to be slower than operator[]; I'd expect them to be about the same speed. The difference is that at() specifies exactly what will happen in there is a bounds error (an exception), where as in the case of operator[], it is undefined behavior — a crash in all of the systems I use (g++ and VC++), at least when the normal debugging flags are used. (Another difference is that once I'm sure of my code, I can get a substantial speed increase for operator[] by turning the debugging off. If the performance requires it — I wouldn't do it unless it were necessary.)

In practice, at() is rarely appropriate. If the context is such that you know the index may be invalid, you probably want the explicit test (e.g. to return a default value or something), and if you know that it can't be invalid, you want to abort (and if you don't know whether it can be invalid or not, I'd suggest that you specify your function's interface more precisely). There are a few exceptions, however, where the invalid index may result from parsing user data, and the error should cause an abort of the entire request (but not bring the server down); in such cases, an exception is appropriate, and at() will do that for you.


@phresnel operator[] isn't required to do bounds checking, but all good implementations do. At least in debugging mode. The only difference is what they do if the index is out of bounds: operator[] aborts with an error message, at() throws an exception.
@phresnel Most of the code I've delivered has been in "debug" mode. You only turn off checking when performance issues actually require it. (Microsoft pre-2010 was a bit of a problem here, since std::string didn't always work if the checking options didn't correspond with those of the runtime: -MD, and you'd better turn off checking, -MDd, and you'd better have it on.)
I am more of the camp that says "code as sanctioned (guaranteed) by standard"; of course you are free to deliver in debug mode, but when doing cross platform development (including, but not exclusively, the case of same OS, but different compiler versions), relying on the standard is the best bet for releases, and debug mode is considered a tool for the programmer to get that thing mostly correct and robust:)
@phresnel Obviously, you only count on the standard. But if some platform does guarantee a crash for undefined behavior, it would be foolish not to take advantage of it (unless the profiler tells you you can't). You can't never be 100% sure that there is no error in your code, and it's reassuring to know that at least in some specific cases, on some specific platforms, you'll crash, rather than destroy all of the clients data.
On the other hand, if the critical parts of your application are isolated and protected by e.g. exception safety (RAII ftw), then should every single access to operator[] be crippled? E.g., std::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y).... I think enforcing bounds checking on delivered binaries falls under premature pessimization. Imho, it should only be band aid for not-well-engineered code.
l
ltjax

The whole point of using exceptions is that your error handling code can be further away.

In this specific case, user input is indeed a good example. Imagine you want to semantically analyze an XML data-structure which uses indices to refer to some kind of resource you internally store in a std::vector. Now the XML tree is a tree, so your probably want to use recursion to analyze it. Deep down, in the recursion, there might be an access violation by the writer of the XML file. In that case, you usually want to bump out of all the levels of recursion and just reject the whole file (or any kind of "coarser" structure). This is where at comes in handy. You can just write the analysis code as-if the file was valid. The library code will take care of the error detection and you can just catch the error on the coarse level.

Also, other containers, like std::map, also have std::map::at which has slightly different semantics than std::map::operator[]: at can be used on a const map, while operator[] cannot. Now if you wanted to write container agnostic code, like something that could deal with either const std::vector<T>& or const std::map<std::size_t, T>&, ContainerType::at would be your weapon of choice.

However, all these cases usually appear when handling some kind of unvalidated data-input. If you are sure about your valid range, as you usually should be, you can usually use operator[], but better yet, iterators with begin() and end().


a
ahj

According to this article, performance aside, it doesn't make any difference to use at or operator[], only if the access is guaranteed to be within the size of the vector. Otherwise, if access is just based on the capacity of the vector it is safer to use at.


out there be dragons. what happens if we click that link? (hint: I know it already, but on StackOverflow we prefer comments that don't suffer link rot, i.e. provide a short summary about what you want to say)
Thanks for the tip. It is fixed now.
S
Shital Shah

Note: It appears some new folks are downvoting this answer without having courtesy of telling what is wrong. Below answer is correct and can be verified here.

There is really only one difference: at does bounds checking while operator[] doesn’t. This applies to debug builds as well as release builds and this is very well specified by the standards. It’s that simple.

This makes at a slower method but it’s also really bad advice to not to use at. You have to look at absolute numbers, not relative numbers. I can safely bet that most of your code is doing fat more expensive operations than at. Personally, I try to use at because I don’t want a nasty bug to create undefined behavior and sneak in to production.


Exceptions in C++ are meant to be an error handling mechanism, not a tool for debugging. Herb Sutter explains why throwing std::out_of_range or any form of std::logic_error is, in fact, a logic error in and of itself here.
@BigTemp - I'm not sure how your comment related to this question and answer. Yes, exceptions are highly debated topic but the question here is the difference between at and [] and my answer simply states the difference. I personally use "safe" method when perf is not an issue. As Knuth says don't do premature optimization. Also, it's good to get bugs caught early than in production regardless of philosophical differences.
I also think that it's better to use at as long as it's not in a very performance sensitive part of the code. It's so much better to get an exception thrown immediately instead of the program continuing with bogus data which might cause much worse problems than a non noticeable performance difference.