Я знаю, что at()
это медленнее, чем []
из-за проверки границ, которая также обсуждается в подобных вопросах, таких как C ++ Vector at / [] operator 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 над vector :: operator [] ?
Когда мне следует использовать vector :: at, а не vector :: size + vector :: operator [] ?
if (i < v.size()) v[i] = 2;
существует возможный путь кода, который вообще не назначается2
ни одному элементуv
. Если это правильное поведение, отлично. Но часто нет ничего разумного, что эта функция может делать, когдаi >= v.size()
. Таким образом, нет особой причины, по которой он не должен использовать исключение для указания неожиданной ситуации. Многие функции просто используютoperator[]
без проверки размера, документ, которыйi
должен быть в диапазоне, и возлагают ответственность за полученный UB на вызывающего.Ответы:
Я бы сказал, что
vector::at()
генерируемые исключения на самом деле не предназначены для перехвата непосредственно окружающим кодом. В основном они полезны для поиска ошибок в вашем коде. Если вам нужно выполнить проверку границ во время выполнения, потому что, например, индекс поступает из пользовательского ввода, вам действительно лучше всего использоватьif
оператор. Таким образом, проектируйте свой код так, чтобыvector::at()
он никогда не генерировал исключение, так что если оно произойдет и ваша программа прервется, это будет признаком ошибки. (точно так жеassert()
)источник
size()
+,[]
когда индекс зависит от ввода пользователя, использоватьassert
в ситуациях, когда индекс никогда не должен выходить за пределы для легкого исправления ошибок в будущем, и.at()
во всех других ситуациях (на всякий случай может произойти что-то не так ... .)vector
то, вероятно, лучше использовать это как вариант «на всякий случай», а неat()
везде. Таким образом, вы можете надеяться на немного более высокую производительность в режиме выпуска, на всякий случай, если она вам когда-нибудь понадобится.operator[]
, например gcc.gnu.org/onlinedocs/libstdc++/manual/… так что, если ваша платформа поддерживает это, вам, вероятно, лучше всего использовать его!Нет, это не так (блок try / catch может быть восходящим). Это полезно, когда вы хотите, чтобы генерировалось исключение, а не ваша программа входила в область неопределенного поведения.
Я согласен с тем, что в большинстве случаев доступ к векторам вне пределов является ошибкой программиста (в этом случае вы должны использовать
assert
этот метод для более легкого обнаружения этих ошибок; большинство отладочных версий стандартных библиотек делают это автоматически за вас). Вы не хотите использовать исключения, которые могут быть проглочены вверх по течению, чтобы сообщить об ошибках программиста: вы хотите иметь возможность исправить ошибку .Поскольку маловероятно, что доступ за пределы вектора является частью нормального потока программы (в этом случае вы правы: проверьте заранее,
size
вместо того, чтобы позволить исключению всплыть), я согласен с вашей диагностикой:at
по сути бесполезно.источник
out_of_range
исключение,abort()
вызывается.try..catch
могут присутствовать в методе, вызывающем этот метод.at
это полезно в той степени, в которой вы в противном случае сочли бы, что пишете что-то подобноеif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
. Люди часто думают о функциях генерирования исключений как о «проклятиях, я должен обрабатывать исключение», но если вы тщательно документируете, что может вызывать каждая из ваших функций, их также можно использовать как «отлично, я не необходимо проверить условие и выбросить исключение ».out_of_range
ж , официальный ответ на это заключается в томlogic_error
, что другие программисты "должны" знать лучше, чем ловитьlogic_error
апстрим и игнорировать их.assert
также можно проигнорировать, если ваши коллеги не хотят знать о своих ошибках, это просто сложнее, потому что им приходится компилировать ваш кодNDEBUG
;-) Каждый механизм имеет свои достоинства и недостатки.Важным моментом здесь является то, что исключения позволяют отделить нормальный поток кода от логики обработки ошибок, и один блок catch может обрабатывать проблемы, сгенерированные любым из бесчисленных сайтов выдачи, даже если они разбросаны глубоко внутри вызовов функций. Таким образом, это не
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
В такой ситуации очень сложно проверить, не дошли ли вы до конца ввода ненадлежащим образом, потому что это очень зависит от точных обнаруженных токенов. Явная проверка в каждой точке использования болезненна, и существует гораздо больше возможностей для ошибки программиста, поскольку срабатывают приращения до / после, смещения в точке использования, ошибочные рассуждения о продолжающейся достоверности некоторых более ранних тестов и т. Д.
источник
at
может быть понятнее, если у вас есть указатель на вектор:return pVector->at(n); return (*pVector)[n]; return pVector->operator[](n);
Помимо производительности, первое из них - это более простой и понятный код.
источник
Во- первых, независимо от того
at()
илиoperator[]
медленнее , не уточняется. Когда нет ошибки границ, я бы ожидал, что они будут примерно с такой же скоростью, по крайней мере, при отладке сборок. Разница в том, чтоat()
точно определяет, что произойдет, при наличии ошибки границ (исключение), где, как и в случаеoperator[]
, это неопределенное поведение - сбой во всех используемых мной системах (g ++ и VC ++), по крайней мере, когда используются обычные отладочные флаги. (Еще одно отличие состоит в том, что как только я уверен в своем коде, я могу получить существенное увеличение скоростиoperator[]
, отключив отладку. Если этого требует производительность - я бы не стал этого делать, если в этом нет необходимости.)На практике
at()
бывает редко. Если контекст таков, что вы знаете, что индекс может быть недействительным, вам, вероятно, нужен явный тест (например, чтобы вернуть значение по умолчанию или что-то еще), и если вы знаете, что он не может быть недопустимым, вы хотите прервать его (и если вы не знаете, может ли он быть недействительным, я бы посоветовал вам более точно указать интерфейс вашей функции). Однако есть несколько исключений, когда недействительный индекс может быть результатом синтаксического анализа пользовательских данных, и ошибка должна привести к прерыванию всего запроса (но не к остановке сервера); в таких случаях уместно исключение,at()
которое сделает это за вас.источник
operator[]
не принудительно проверять границы, аat()
есть? Вы под этим подразумеваете проблемы с кешированием, предположениями и буфером ветвления?at()
.std::string
не всегда работала, если параметры проверки не соответствовали параметрам среды выполнения:,-MD
и вам лучше отключить проверку-MDd
, и вам лучше это дальше.)operator[]
ли ограничивать каждый доступ ? Например,std::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y)...
. Я думаю, что принудительная проверка границ для доставленных двоичных файлов подпадает под преждевременную пессимизацию. Имхо, это должно быть только повязкой для плохо спроектированного кода.Весь смысл использования исключений в том, что ваш код обработки ошибок может быть дальше.
В этом конкретном случае ввод пользователя действительно является хорошим примером. Представьте, что вы хотите семантически проанализировать структуру данных XML, которая использует индексы для ссылки на какой-то ресурс, который вы внутренне храните в файле
std::vector
. Теперь XML-дерево представляет собой дерево, поэтому вы, вероятно, захотите использовать рекурсию для его анализа. Глубоко внутри рекурсии могло быть нарушение прав доступа со стороны автора XML-файла. В этом случае вы обычно хотите выйти из всех уровней рекурсии и просто отвергнуть весь файл (или любую «более грубую» структуру). Вот здесь и пригодится. Вы можете просто написать код анализа, как если бы файл был действительным. Код библиотеки позаботится об обнаружении ошибок, и вы можете просто обнаружить ошибку на грубом уровне.Кроме того, другие контейнеры, например
std::map
, также имеютstd::map::at
семантику, немного отличающуюся отstd::map::operator[]
: at, могут использоваться на константной карте, аoperator[]
не могут. Теперь, если вы хотите написать код, не зависящий от контейнера, например, что-то, что может иметь дело с любымconst std::vector<T>&
илиconst std::map<std::size_t, T>&
,ContainerType::at
было бы вашим оружием.Однако все эти случаи обычно возникают при обработке некорректного ввода данных. Если вы уверены в своем допустимом диапазоне, как обычно, вы можете использовать
operator[]
, но еще лучше, итераторы сbegin()
иend()
.источник
Согласно этой статье, не считая производительности, нет никакой разницы в использовании
at
илиoperator[]
, только если доступ гарантирован в пределах размера вектора. В противном случае, если доступ основан только на емкости вектора, его безопаснее использоватьat
.источник
Примечание. Похоже, некоторые новички голосуют против этого ответа, не любезно сообщая, что не так. Ниже приведен правильный ответ, который можно проверить здесь .
На самом деле есть только одно отличие:
at
проверяет ли границы, аoperator[]
нет. Это применимо как к сборкам отладки, так и к сборкам выпуска, и это очень хорошо определено стандартами. Это так просто.Это
at
более медленный метод, но также плохой совет не использоватьat
. Вы должны смотреть на абсолютные числа, а не на относительные числа. Могу спокойно поспорить, что большая часть вашего кода выполняет более дорогие операции, чемat
. Лично я стараюсь использовать,at
потому что не хочу, чтобы неприятная ошибка создавала неопределенное поведение и пробиралась в производство.источник
std::out_of_range
или любую другую формуstd::logic_error
, на самом деле, логическую ошибку самого по себе здесь .at
и,[]
и мой ответ просто указывает на разницу. Я лично использую «безопасный» метод, когда производительность не является проблемой. Как говорит Кнут, не делайте преждевременной оптимизации. Кроме того, хорошо обнаруживать ошибки раньше, чем в производственной среде, независимо от философских различий.