Почему допустимо поведение undefined в STL?

По умолчанию "базовый контейнер" std::stack - это std::deque. Поэтому все, что есть undefined поведение для std::deque, - это поведение undefined для std::stack. cppreference, а другие сайты используют терминологию "эффективно" при описании поведения функций-членов. Я полагаю, это означает, что это предназначено для всех целей и целей. Таким образом, вызов top() и pop() эквивалентен вызовам back() и pop_back(), а вызов их в пустом контейнере - это поведение undefined.

По моему мнению, причина, по которой это поведение undefined заключается в том, чтобы сохранить гарантию отсутствия броска. Мое рассуждение состоит в том, что operator[] для std::vector имеет гарантию отсутствия броска и имеет значение undefined, если размер контейнера больше N, но at() имеет сильную гарантию и бросает std::out_of_range, если n не входит в границы.

Итак, мой вопрос в том, что является причиной некоторых вещей, которые могут иметь поведение undefined и не имеют никакой гарантии броска по сравнению с сильной гарантией, но вместо этого выбрасывают исключение?

Ответ 1

Когда поведение undefined разрешено, оно обычно по соображениям эффективности.

Если в стандарте указано, что должно произойти при доступе к массиву за пределами границ, это заставит реализацию проверить, находится ли индекс в границах. То же самое относится к вектору, который является просто оболочкой для динамического массива.

В других случаях поведение допускается undefined, чтобы обеспечить свободу в реализации. Но это тоже касается эффективности (поскольку некоторые возможные стратегии реализации могут быть более эффективными на некоторых машинах, чем на других, а С++ оставляет за собой возможность реализовать наиболее эффективную стратегию, если они этого желают.)

Ответ 2

Согласно Herb Sutter, одна из отмеченных причин - эффективность. Он утверждает, что стандарт не налагает никаких требований в спецификации спецификации operator[] или не требует проверки привязки. Это зависит от реализации.

С другой стороны, vector<T>::operator[]() разрешено, но не требуется, чтобы выполнить проверку границ. Там нет дыхания формулировки в стандартной спецификации для operator[](), которая говорит что-либо о проверке границ, но и нет никаких требований, чтобы это имеют спецификацию исключения, поэтому ваш стандартный исполнятель библиотеки можно также добавить проверку границ на operator[](). Итак, если вы используете operator[]() запросить элемент, который не находится в векторе, вы по своему усмотрению, и стандарт не дает никаких гарантий относительно того, что будет (хотя ваша стандартная документация по внедрению библиотеки может) - ваша программа может немедленно сработать, вызов operator[]() может генерировать исключение, или может показаться, что работа и иногда и/или таинственным образом терпит неудачу.

Учитывая, что проверка границ защищает нас от многих распространенных проблем, почему для выполнения проверки границ требуется operator[]()? Короткий ответ: Эффективность. Всегда проверка границ приведет к (возможно, незначительные) накладные расходы на производительность для всех программ, даже тех, которые никогда не нарушайте границы. Дух C++ включает в себя изречение, которое и вам не придется платить за то, что вы не используете, и поэтому проверка границ не требуется для operator[](). В этом случае мы имеют дополнительную причину для достижения эффективности: векторы предназначены которые будут использоваться вместо встроенных массивов, и поэтому должны быть такими же эффективными как встроенные массивы, которые не выполняют проверку границ. Если ты хочешь быть убедитесь, что границы проверяются, вместо этого используйте at().

Если вам интересно узнать о преимуществах производительности, см. следующие два вопроса:

Похоже, что консенсус operator[] более эффективен (поскольку std::vector - это всего лишь оболочка вокруг динамического массива, operator[] должна быть такой же эффективной, как если бы вы назовете ее на массив.) И Herb Саттер, похоже, полагает, что независимо от того, безопасно это исключение, зависит от поставщика компилятора.