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


95

Я знаю, що at()це повільніше, ніж []через перевірку меж, що також обговорюється в подібних питаннях, таких як C ++ Vector at / [] оператор швидкість або :: 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 :: operator [] ?
Коли слід використовувати оператор vector :: at замість vector :: size + vector :: [] ?


11
+1 дуже гарне запитання !! але я не думаю, що () часто використовується.
Рохіт Віпін Метьюз

10
Зверніть увагу, що у вашому прикладі коду if (i < v.size()) v[i] = 2;існує можливий шлях коду, який не призначається 2жодному елементу vвзагалі. Якщо це правильна поведінка, чудово. Але часто немає нічого розумного, що ця функція може робити коли i >= v.size(). Отже, немає особливих причин, чому він не повинен використовувати виняток для позначення несподіваної ситуації. Багато функцій просто використовують, operator[]не перевіряючи розмір, документ, який iповинен бути в діапазоні, і звинувачують отриманий UB у абонента.
Steve Jessop

Відповіді:


74

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


1
+1 Мені подобається пояснення того, як відокремити обробку вводу неправильного користувача (перевірка вводу; неправдиве введення може очікуватися, тому це не розглядається як щось виняткове) ... та помилки в коді (ітератор розмежування, який виходить за межі діапазону, є винятковим штука)
Боян Комазец

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

8
@LihO: якщо ваша реалізація пропонує реалізацію налагодження, vectorтоді, мабуть, краще використовувати це як варіант "про всяк випадок", а не at()скрізь. Таким чином, ви можете сподіватися на трохи більшу продуктивність у режимі випуску, на той випадок, якщо вам коли-небудь знадобиться.
Steve Jessop

3
Так, більшість реалізацій STL сьогодні підтримують режим налагодження, який навіть перевіряє межі operator[], наприклад, gcc.gnu.org/onlinedocs/libstdc++/manual/ ... так що, якщо ваша платформа це підтримує, вам, мабуть, краще з цим піти!
pmdj

1
@pmdj фантастичний момент, про який я не знав ... але осиротіле посилання. : P поточний: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
underscore_d

16

це змушує мене обернути його блоком try-catch

Ні, це не так (блок try / catch може бути вище). Це корисно, коли ви хочете, щоб було створено виняток, а не ваша програма для введення невизначеної сфери поведінки.

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

Оскільки навряд чи доступ до вектора за межами межі є частиною звичайного потоку програм (у такому випадку ви маєте рацію: попередньо перевірте, sizeзамість того, щоб пускати виняток), я погоджуюсь з вашою діагностикою: atє по суті марним.


Якщо я не вловлю out_of_rangeвиняток, тоді abort()викликається.
LihO

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

12
Якщо ніщо інше, atкорисно в тій мірі, в якій ви б інакше опинились у написанні чогось подібного if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }. Люди часто думають про функції викидів з точки зору "прокляття, я повинен обробляти виняток", але якщо ви ретельно задокументуєте, що може зробити кожна з ваших функцій, їх також можна використовувати як "чудові, я не повинні перевірити стан і викинути виняток ".
Steve Jessop

@SteveJessop: Мені не подобається створювати винятки для програмних помилок, оскільки їх можуть перехопити інші програмісти. Твердження тут набагато корисніші.
Александр К.

6
@AlexandreC. ну, офіційна відповідь на це полягає в тому, що out_of_rangeпоходить від logic_error, а інші програмісти "повинні" знати це краще, ніж ловити logic_errors вище за течією та ігнорувати їх. assertтакож можна ігнорувати, якщо ваші колеги прагнуть не знати про свої помилки, це просто складніше, тому що вони повинні компілювати ваш код NDEBUG;-) Кожен механізм має свої достоїнства та недоліки.
Steve Jessop

11

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

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

Також примітно, що в деяких типах коду індекс збільшується складними способами і постійно використовується для пошуку масиву. У таких випадках набагато легше забезпечити правильну перевірку використання at().

Як приклад у реальному світі, я маю код, який символізує С ++ на лексичні елементи, а потім інший код, який переміщує індекс над вектором лексем. Залежно від того, що трапилось, я можу збільшити та перевірити наступний елемент, як у:

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

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


10

at може бути зрозумілішим, якщо у вас є вказівник на вектор:

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

Окрім продуктивності, першою з них є простіший і зрозуміліший код.


... особливо, коли вам потрібен вказівник на n -й елемент вектора.
дельфін

4

По-перше, не вказано, повільніше at()чи operator[]повільніше. Коли помилок меж немає, я очікував би, що вони мають приблизно однакову швидкість, принаймні в налагоджувальних збірках. Різниця полягає в тому at(), що точно вказується, що відбуватиметься, є помилка меж (виняток), де, як і у випадку operator[], це невизначена поведінка - збій у всіх використовуваних мною системах (g ++ та VC ++), принаймні коли використовуються звичайні прапорці налагодження. (Інша відмінність полягає в тому, що як тільки я впевнений у своєму коді, я можу отримати значне збільшення швидкості operator[] , вимкнувши налагодження. Якщо це вимагає продуктивність - я б не робив цього, якщо це не було потрібно.)

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


4
Чому ви очікуєте, що вони матимуть приблизно однакову швидкість, коли operator[]не змушений перевіряти обмеження, тоді як at()це? Ви під цим маєте на увазі проблеми кешування, спекуляції та розгалуження буфера?
Себастьян Мах,

@phresnel operator[]не вимагає перевірки меж, але всі хороші реалізації роблять це. Принаймні в режимі налагодження. Єдина різниця полягає в тому, що вони роблять, якщо індекс виходить за межі: operator[]переривається з повідомленням про помилку, at()видає виняток.
James Kanze,

2
На жаль, пропустив свій атрибут "у режимі налагодження". Однак я б не вимірював код за його якістю в режимі налагодження. У режимі звільнення перевірка потрібна лише at().
Себастьян Мах

1
@phresnel Більшість коду, який я доставив, перебуває в режимі "налагодження". Ви вимикаєте перевірку лише тоді, коли проблеми з продуктивністю насправді цього вимагають. (Microsoft до 2010 р. Тут була проблемою, оскільки std::stringне завжди спрацьовувала, якщо параметри перевірки не відповідали параметрам виконання:, -MDі краще відключити перевірку -MDd, та краще б мати він включений).
Джеймс Kanze

2
Я більше з табору, який говорить "код, санкціонований (гарантований) за стандартом"; звичайно, ви можете безкоштовно здійснювати налагодження в режимі налагодження, але при розробці крос-платформних платформ (включаючи, але не виключно випадок однієї і тієї ж ОС, але різні версії компілятора), найкращим вибором для випусків і режиму налагодження є використання стандарту. вважається інструментом для програміста отримати цю річ переважно правильною та надійною :)
Себастьян Мах

1

Вся суть використання винятків полягає в тому, що ваш код обробки помилок може бути далі.

У цьому конкретному випадку введення користувача справді є гарним прикладом. Уявіть, що ви хочете семантично проаналізувати структуру даних 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().


1

Згідно з цією статтею, якщо не брати до уваги продуктивність, вона не має жодної різниці у використанні atабо operator[]лише в тому випадку, якщо доступ гарантовано буде в межах розміру вектора. В іншому випадку, якщо доступ базується лише на потужності вектора, використовувати його безпечніше at.


1
там дракони. що станеться, якщо ми натиснемо це посилання? (підказка: я це вже знаю, але на StackOverflow ми віддаємо перевагу коментарям, які не страждають від гниття посилань, тобто надаємо короткий підсумок про те, що ви хочете сказати)
Себастьян Мах

Дякую за підказку. Зараз це виправлено.
ahj

0

Примітка: Схоже, деякі нові люди проти цієї відповіді голосують, не маючи ввічливості сказати, що не так. Нижче відповідь правильна і її можна перевірити тут .

Різниця насправді є лише одна: atперевіряє межі, а operator[]не робить. Це стосується збірок налагодження, а також збірок випусків, і це дуже добре визначено стандартами. Це все так просто.

Це робить atметод повільнішим, але це також дуже погана порада не використовувати at. Ви повинні дивитись на абсолютні числа, а не на відносні. Я можу сміливо переконатись, що більша частина вашого коду робить більш дорогі операції, ніж at. Особисто я намагаюся використовувати, atтому що не хочу, щоб неприємна помилка створювала невизначену поведінку та підкрадалася до виробництва.


1
Винятки в C ++ мають бути механізмом обробки помилок, а не інструментом для налагодження. Herb Sutter пояснює , чому кидати std::out_of_rangeабо будь-яку іншу форму std::logic_error, насправді, логічну помилку самого по собі тут .
Big Temp

@BigTemp - Я не впевнений, як ваш коментар стосувався цього питання та відповіді. Так, винятки - це тема, яка дуже обговорюється, але питання тут полягає у різниці між atі []і моя відповідь просто констатує різницю. Я особисто використовую "безпечний" метод, коли перформанс не є проблемою. Як каже Кнут, не робіть передчасну оптимізацію. Крім того, добре ловити помилок раніше, ніж на виробництві, незалежно від філософських відмінностей.
Шитал Шах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.