2012-02-21 10 views
52

Я знаю, что at() медленнее, чем [] из-за его граничной проверки, что также обсуждается в похожих вопросах, таких как C++ Vector at/[] operator speed или ::std::vector::at() vs operator[] << surprising results!! 5 to 10 times slower/faster!. Я просто не понимаю, для чего подходит метод at().vector :: at vs. vector :: operator []

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

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

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

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

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

+6

+1 очень хороший вопрос !! но я не думаю, что в() это обычно используется. –

+9

Обратите внимание, что в вашем примере кода 'if (i = v.size()'. Поэтому нет особых причин, по которым * не следует использовать исключение, чтобы указать на непредвиденную ситуацию. Многие функции просто используют 'operator []' без проверки на размер, документ, который 'i' должен находиться в зоне действия, и обвинять полученный UB в вызывающем. –

ответ

37

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

+0

+1 Мне нравится объяснение того, как разделить обработку неправильного ввода пользователя (проверка ввода, недопустимый ввод может ожидаться, поэтому не рассматривается как нечто исключительное) ... и ошибки в коде (разыменование итератора, выходящего за пределы диапазона, исключительная вещь) –

+0

Итак, вы говорите, что я должен использовать 'size()' + '[]', когда индекс зависит от ввода пользователя, используйте 'assert' в ситуациях, когда индекс никогда не должен быть за пределами границ для легкого исправления ошибок в будущем и '.at()' во всех других ситуациях (на всякий случай, может произойти что-то неправильное) – LihO

+7

@LihO: если ваша реализация предлагает отладочную реализацию 'vector', то, вероятно, лучше использовать это как« просто в случае ", а не' at() 'всюду. Таким образом, вы можете надеяться на более высокую производительность в режиме выпуска, на случай, если вам это понадобится. –

12

это заставляет меня, чтобы обернуть его примерки поймать блок

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

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

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

+0

Если я не поймаю исключение 'out_of_range', вызывается' abort() '. – LihO

+0

@LihO: Не обязательно .. 'try..catch' может присутствовать в методе, который вызывает этот метод. – Naveen

+9

Если ничего больше, 'at' полезно в той мере, в какой вы в противном случае могли бы написать что-то вроде' if (i

8

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

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

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

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

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

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

1

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

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

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

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

5

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

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

+2

Почему вы ожидаете, что они будут иметь одинаковую скорость, когда 'operator []' не принуждается к ограничениям - check, тогда как 'at()' is? Вы тем, что подразумевает проблемы кэширования, спекуляции и разветвления? –

+0

@phresnel 'operator []' не требуется выполнять проверку границ, но все хорошие реализации делают. По крайней мере, в режиме отладки. Единственное различие заключается в том, что они делают, если индекс за пределами границ: 'operator []' прерывается с сообщением об ошибке, 'at()' генерирует исключение. –

+2

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

0

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

+0

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

+0

Спасибо за отзыв. Теперь это исправлено. – ahj

4

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

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

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

+0

...особенно если вам нужен указатель на _n_-й элемент вектора. – dolphin

-1

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

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

+0

Не знаете, почему люди безвыходны. Выше ответ правильный и может быть проверен с помощью ссылки на C++. – ShitalShah