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

Я знаю, что at() медленнее, чем [] из-за его проверки границ, что также обсуждается в похожих вопросах, таких как С++ Vector at/[] speed оператора или :: std::vector:: at() vs operator [] < < удивительные результаты! От 5 до 10 раз медленнее/быстрее!. Я просто не понимаю, для чего подходит метод at().

Если у меня есть простой вектор, подобный этому: std::vector<int> v(10);, и я решаю получить доступ к его элементам, используя at() вместо [] в ситуации, когда у меня есть индекс i, и я не уверен, что его в границах векторов, он заставляет меня обернуть его блоком try-catch:

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

хотя я могу сделать то же самое поведение, используя size() и проверять индекс самостоятельно, что кажется мне проще и удобнее для меня:

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

Итак, мой вопрос:
Преимущества использования vector:: at over vector:: оператор []?
Когда следует использовать vector:: at, а не vector:: размер + vector:: operator []?

Ответ 1

Я бы сказал, что исключения, которые vector::at() бросают, на самом деле не предназначены для того, чтобы их поймал сразу же окружающий код. Они в основном полезны для обнаружения ошибок в коде. Если вам нужно проверить границы во время выполнения, например, индекс поступает от пользовательского ввода, вы действительно лучше всего выполняете оператор if. Итак, вкратце, создайте свой код с намерением, чтобы vector::at() никогда не выдавал исключение, так что если это произойдет, и ваша программа прерывается, это признак ошибки. (точно так же, как assert())

Ответ 2

он заставляет меня обернуть его блоком try-catch

Нет, это не так (блок try/catch может быть выше по течению). Это полезно, если вы хотите, чтобы исключение было выбрано, а не ваша программа, чтобы войти в область поведения undefined.

Я согласен с тем, что большинство вне доступа к векторам является ошибкой программиста (в этом случае вам следует использовать assert, чтобы легче находить эти ошибки, большинство отладочных версий стандартных библиотек делают это автоматически для вас). Вы не хотите использовать исключения, которые можно усвоить вверх, чтобы сообщить ошибки программиста: вы хотите иметь возможность исправить ошибку.

Поскольку маловероятно, что доступ за пределы доступа к вектору является частью нормального потока программы (в случае, если это так, вы правы: предварительно проверьте size, вместо того, чтобы освободить исключение) Я согласен с вашей диагностикой: at по существу бесполезен.

Ответ 3

В чем преимущества использования vector:: at over vector:: operator []? Когда следует использовать вектор:: вместо, а не вектор:: размер + вектор:: оператор []?

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

Также следует отметить, что в некоторых типах кода индекс инкрементируется сложными способами и постоянно используется для поиска массива. В таких случаях гораздо проще обеспечить правильные проверки с помощью at().

Как реальный пример, у меня есть код, который токенизирует С++ в лексических элементах, а затем другой код, который перемещает индекс по вектору токенов. В зависимости от того, что встречалось, я могу увеличить и проверить следующий элемент, как в:

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

В такой ситуации очень сложно проверить, не был ли вы ненадлежащим образом достигли конца ввода, потому что это очень зависит от точных жетонов. Явная проверка в каждой точке использования является болезненной, и там гораздо больше места для ошибки программиста, так как приращения до/после, смещения в точке использования, ошибочные рассуждения о продолжающейся достоверности некоторых более ранних тестов и т.д. Нажмите.

Ответ 4

Во-первых, не медленнее ли at() или operator[]. когда нет ошибок в границах, я ожидаю, что они будут примерно одинаковой скорости, на в наименьшей степени в отладочных сборках. Разница в том, что at() указывает точно, что произойдет в результате ошибки границ (исключение), где, как и в случае operator[], это undefined поведение — a авария во всех системах, которые я использую (g++ и VС++), по крайней мере, когда используются обычные флаги отладки. (Еще одно отличие состоит в том, что когда я уверен в моем коде, я могу получить значительное увеличение скорости для operator[] отключив отладку. Если производительность требует этого, я не сделал бы этого, если бы это было необходимо.)

На практике at() редко бывает подходящим. Если контекст таков, что вы знаете, что индекс может быть недействительным, вероятно, вам нужен явный тест (например, вернуть значение по умолчанию или что-то еще), и если вы знаете, что это не может быть недействительным, вы хотите прервать (и если вы не знаете, может быть недействительным или нет, я бы предложил вам указать функцию интерфейс более точно). Однако есть несколько исключений, где неверный индекс может возникнуть в результате анализа данных пользователя, а ошибка должен вызвать прерывание всего запроса (но не привести сервер вниз); в таких случаях исключение является подходящим, и at() будет делать это для вас.

Ответ 5

at может быть более четким, если у вас есть указатель на вектор:

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

Производительность в стороне, первый из них - более простой и понятный код.

Ответ 6

Весь смысл использования исключений заключается в том, что ваш код обработки ошибок может быть удален.

В этом конкретном случае пользовательский ввод действительно хороший пример. Представьте, что вы хотите семантически анализировать структуру данных XML, которая использует индексы для ссылки на какой-то ресурс, который вы храните внутри std::vector. Теперь дерево XML - это дерево, поэтому, вероятно, вы захотите использовать рекурсию для ее анализа. В глубине, в рекурсии, может быть нарушение доступа писателем XML файла. В этом случае вы обычно хотите вырваться из всех уровней рекурсии и просто отклонить весь файл (или любую более "грубую" структуру). Здесь и пригодится. Вы можете просто написать код анализа, как если бы файл был действительным. Код библиотеки позаботится об обнаружении ошибок, и вы можете просто поймать ошибку на грубом уровне.

Кроме того, в других контейнерах, таких как std::map, также есть std::map::at, который имеет немного отличную семантику, чем std::map::operator[]: at может использоваться на карте const, а operator[] не может. Теперь, если вы хотите написать агностический код контейнера, например, что-то, что может иметь дело с const std::vector<T>& или const std::map<std::size_t, T>&, ContainerType::at будет вашим оружием выбора.

Тем не менее, все эти случаи обычно возникают при обработке некоторого неутвержденного ввода данных. Если вы уверены в своем допустимом диапазоне, как обычно, вы обычно можете использовать operator[], но еще лучше, итераторы с begin() и end().

Ответ 7

Согласно этой статье, в отличие от производительности, не имеет никакого значения использовать at или operator[], только если доступ гарантированно будет находиться в пределах размера вектора. В противном случае, если доступ основан только на емкости вектора, безопаснее использовать at.

Ответ 8

Примечание. Похоже, что некоторые новые люди отказываются от ответа на этот вопрос, не имея возможности сказать, что не так. Ниже приведен правильный ответ, который можно проверить здесь.

На самом деле есть только одно отличие: at выполняет проверку границ, а operator[]. Это относится как к отладочным, так и к выпускным версиям, и это очень хорошо определено стандартами. Это так просто.

Это делает at более медленный метод, но его также очень плохой совет, чтобы не использовать at. Вы должны смотреть на абсолютные числа, а не относительные числа. Я могу с уверенностью поспорить, что большая часть вашего кода выполняет более дорогие операции, чем at. Лично я стараюсь использовать at потому, что я не хочу неприятную ошибку, чтобы создать неопределенное поведение и проникнуть в к производству.