11

By default, the "underlying container" of an std::stack is an std::deque. Therefore anything that is undefined behavior for a std::deque is undefined behavior for a std::stack. cppreference and other sites use the terminology "effectively" when describing the behavior of member functions. I take this to mean that it is for all intents and purposes. So therefore, calling top() and pop() is equivalent to calling back() and pop_back(), and calling these on an empty container is undefined behavior.

From my understanding, the reason why it's undefined behavior is to preserve the no-throw guarantee. My reasoning is that operator[] for std::vector has a no-throw guarantee and is undefined behavior if container size is greater than N, but at() has a strong guarantee, and throws std::out_of_range if n is out of bounds.

So my question is, what is the rationale behind some things having possibly undefined behavior and having a no throw guarantee versus having a strong guarantee but throwing an exception instead?

user3358854
  • 113
  • 5

2 Answers2

15

When undefined behaviour is allowed, it's usually for reasons of efficiency.

If the standard specified what has to happen when you access an array out of bounds, it would force the implementation to check whether the index is in bounds. Same goes for a vector, which is just a wrapper for a dynamic array.

In other cases the behaviour is allowed to be undefined in order to allow freedom in the implementation. But that, too, is really about efficiency (as some possible implementation strategies could be more efficient on some machines than on others, and C++ leaves it up to the implementer to pick the most efficient strategy, if they so desire.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
2

According to Herb Sutter one marked reason is efficiency. He states that the standard does not impose any requirements on operator[]'s exception specification or whether or not it requires bound checking. This is up to the implementation.

On the other hand, vector<T>::operator[]() is allowed, but not required, to perform bounds checking. There's not a breath of wording in the standard's specification for operator[]() that says anything about bounds checking, but neither is there any requirement that it have an exception specification, so your standard library implementer is free to add bounds-checking to operator[](), too. So, if you use operator[]() to ask for an element that's not in the vector, you're on your own, and the standard makes no guarantees about what will happen (although your standard library implementation's documentation might) -- your program may crash immediately, the call to operator[]() might throw an exception, or things may seem to work and occasionally and/or mysteriously fail.

Given that bounds checking protects us against many common problems, why isn't operator[]() required to perform bounds checking? The short answer is: Efficiency. Always checking bounds would cause a (possibly slight) performance overhead on all programs, even ones that never violate bounds. The spirit of C++ includes the dictum that, by and large, you shouldn't have to pay for what you don't use, and so bounds checking isn't required for operator[](). In this case we have an additional reason to want the efficiency: vectors are intended to be used instead of built-in arrays, and so should be as efficient as built-in arrays, which don't do bounds-checking. If you want to be sure that bounds get checked, use at() instead.

If you're curious about the performance benefits, see these two questions:

  1. ::std::vector::at() vs operator[] << surprising results!! 5 to 10 times slower/faster!
  2. vector::at vs. vector::operator[]

The consensus seems to be that operator[] is more efficient (since std::vector is just a wrapper around a dynamic array, operator[] should be just as efficient as if you would call it on an array.) And Herb Sutter seems to suggest that whether or not it is exception-safe is up to the compiler-vendor.