Home > Enterprise >  Isn't vector.at(vector.size()-1) better than vector.back() in case it's empty?
Isn't vector.at(vector.size()-1) better than vector.back() in case it's empty?

Time:02-03

Just in case the vector is empty, (unexpected, exceptional case)

cppreference says on vector::back():

Calling back on an empty container causes undefined behavior.

But on vector::at:

If pos is not within the range of the container, an exception of type std::out_of_range is thrown.

So if the container is empty, it's UB if the back() was used, but std::out_of_range is thrown if the at() was used.

Then, isn't it better to use vector.at(vector.size()-1) instead of vector.back() just in case it's empty?

(Since vector.size() will return size_t which is unsigned, vector.size()-1 would be some number like 18446744073709551615, so maybe vector.at(static_cast<int>(vector.size())-1) would be even better, strictly speaking.)

CodePudding user response:

"better" depends on the situation.

back() exists for those cases when you know that the vector is "not v.empty()". In those cases, using at() is just wasting resources. This is a quite common case.

at() on the other hand can be useful when you don't know or care if the supplied index is within bounds. You may have an exception handler installed already so you let that deal with the exceptional event that the index is out of bounds.

CodePudding user response:

If you already know that the vector is empty you would not call either of them.

at()

When you do not know whether the vector is empty and you are not certain that the index you are using is a valid one then you can use at. Though most of the time you should know already that the index is valid (for example there is almost never a good reason to call at inside a loop, when iterating elements).

at should be used only rarely. Most of the time you should know already if there is an element or not and then act accordingly. Checking bounds has a cost, and excpetions are for exceptional cases, they should not be used for control flow.

For example

 int foo(const std::vector<int>& x) { return x.back(); }

You can replace back with at(x.size()-1) but whether this is the right choice needs to be determined from case to case. Other alternatives include (non exhaustive, no particular order):

  • x being non empty is a precondition of foo. Nothing has to be changed. Using back is fine. If someone does not respect preconditions they get what they deserve.
  • foo could return a std::optional<int>. Then no exception is needed. Instead if (x.empty()) return {}; can be added as first line. Once back is called, the vector is already known to be not empty.
  • foo could throw an exception when x is empty. Then you should consider what excpetion to throw. Throwing a generic out_of_bounds exception may be inappropriate. Maybe you want to throw a foo_has_an_issue exception and add as first line if (x.empty()) throw foo_has_an_issue{};

back()

back() is undefined when the vector is empty. You need to check if there is an element before calling it.

size()-1

size() returns an unsigned. When the vector is empty then size()-1 is a large integer, it is certainly not a valid index.

CodePudding user response:

You can check that the vector is not empty before you try to access the back element:

int x = 0;

if (!v.empty())
{
  x = v.back();
}

In the above case the behavior is that x will equal:

  • zero if v is empty, and
  • v.back() if v is not empty.

If you expect that the vector might be empty sometimes in the normal execution of your program, then it should be handled in a similar way to above. You have something defined for x to equal if v is empty.

However, if v should never be empty (i.e.: v being empty represents abnormal behavior) then you should throw your own exception at that point. Rather than relying on std::out_of_range, you can throw something that makes it more obvious what the problem is (i.e.: the problem is that v was empty, not that an index was out of range).

  •  Tags:  
  • c
  • Related