Анимация
JavaScript


Главная  Библионтека 

0 1 [ 2 ] 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Задача 1. Вектор: потребление

и злоупотребление Сложность: 4

Почти все используют std:: vector, и это хорошо. К сожалению, многие не всегда верно понимают его семантику и в результате невольно применяют его странными, а порой и опасными способами. Сколько из перечисленных ниже проблем можно найти в ваших программах?

Вопрос для новичка

1. В чем разница между строками А и В?

void fС vector<int>& v ) {

v[0]; A

v.at(O); В

Вопрос для профессионала

2. Рассмотрим следующий код. vector<int> v;

V. reserveC 2 ) ;

assertC v.capacityO == 2 );

v[0] = 1;

v[l] = 2;

for(vector<i nt>::i terator i = v.beginC); i<v.end(); i++){ cout « *i « endl;

cout « v[0];

V.reserveC 100 );

assertC V.capacityO == 100 );

cout « v[0];

v[2] = 3; v[3] = 4;

v[99i"= 100;

forC vector<int>: : iterator i = v.beginO; i <v. endC) ; i++){ cout « *i « endl;

Раскритикуйте этот код, как с точки зрения стиля, так и с точки зрения корректности.

Решение

Обращение к элементу вектора

1. В чем разница между строками А и В?

void fС vector<int>& v ) {

v[0]; A

v.atCO); в

20 Обобщенное программирование и стандартная библиотека C-t



в примере 1-1, если вектор v не пуст, разницы между строками АиВ нет. Если же V пуст, то в строке В будет гарантированно сгенерировано исключение std: :out of range, но что произойдет в строке А, сказать невозможно.

Имеется два способа обращения к элементам, содержащимся в векторе. Первый, vector<T>::at, выполняет проверку диапазона значения индекса, чтобы убедиться, что требуемый элемент действительно содержится в векторе. Не имеет никакого смысла обращение к сотому элементу вектора, в котором содержится всего 10 элементов, и если вы попытаетесь сделать это, то функция at защитит вас от неверных действий, генерируя исключение std: :out..of range.

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

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

Увеличение размера вектора

Теперь обратимся к примеру 1-2, который работает с vector<int> при помощи некоторых простых операций. 2. Рассмотрим следующий код.

vector<int> v;

V.reserveC 2 );

assert( V.capacityO == 2 ) ;

Раскритикуйте этот код, как с точки зрения стиля, так и с точки зрения корректности.

Данная проверка связана с двумя проблемами, смысловой и стилистической.

Смысловая проблема состоит в том, что проверка может сработать неверно. Почему? Потому что вызов reserve гарантирует, >гго емкость вектора становится равной как минимум 2, но может быть и больше 2. Обычно это так и есть, потому что типичная реализация вектора может всегда увеличивать внутренний буфер экспоненциально, невзирая на конкретный запрос посредством функции reserve. Поэтому корректная проверка датжна использовать оператор сравнения >=, а не строгое равенство.

assert( V.capacityO >= 2 ) ;

Во-вторых, стилистическая ошибка заключается в том, что проверка избыточна. Почему? Потому что стандарт гарантирует выполнение проверяемого условия. Зачем же нужна явная проверка? Она не имеет смысла, если только вы не подозреваете о



наличии ошибок в используемой вами реализации стандартной библиотеки и стремитесь избежать больших проблем.

= 1;

= 2;

Обе эти строки -- грубейшие, но трудно обнаруживаемые ошибки, поскольку такая программа вполне может "работать" (в зависимости от используемой конкретной реализации библиотеки).

Имеется сушественная разница между футткциями size/resize и capacity/reserve, т.е. между размером и емкостью вектора.

• size говорит нам, сколько элементов содержится в контейнере в настоящее время, а resize изменяет содержимое контейнера таким образом, чтобы он содержал указанное количество элементов в контейнере путем добавления или удаления их из конца контейнера. Обе эти функции присутствуют в контейнерах list, vector и deque и отсутствуют в остальных.

• capaci ty возвращает количество мест для элементов в контейнере, т.е. указывает, сколько элементов можно разместить в контейнере перед тем, как добавление очередного элемента вызовет выделение нового блока памяти. Функция reserve при необходимости увеличивает (но никогда не уменьшает) размер внугрсннего буфера , чтобы он был способен вместить как минимум указанное количество элементов. Обе функции предусмотрены только у контейнера vector.

В нашем случае мы использовали вызов v. reserve(2) и, таким образом, гарантировали, что V. capaci tyC) >= 2, но мы не добавляли элементы в вектор v, так что вектор V остается пуст! На данный момент все, что можно сказать о векторе --- это то, что в нем есть место как минимум для двух элементов.

> Рекомендация

Помните о разнице между size/resize и capacity/reserve.

Мы можем безопасно использовать оператор operator[] (или функцию at) только для изменения элементов, которые реально содержатся в контейнере, т.е. реально учтены в size. Вы можете удивиться, почему оператор operator[] не может быть достаточно интеллектуальным, чтобы добавить элемент в контейнер, если он еще не в контейнере. Но если бы operator!] позволял делать такие веши, мы бы могли создавать вектор с "дырами"! Рассмотрим, например, следующий фрагмент.

vector<int> v; V.reserveC 100 );

vL99] =42; Ошибка, но, допустим, такое возможно... ... что тогда можно сказать о значениях v[0..98]?

Увы, поскольку не предусмотрено, чтобы оператор ореrator [] выполнял проверку диапазона, в большинстве рссшизаций выражение v[0] будет просто возвращать ссылку на еще неиспользуемую память во внутреннем буфере вектора, а именно на то место в памяти, где в конечном итоге будет находиться первый элемент вектора. Следовательно, скорее всего, инструкция v[0] = 1; будет "[юрмально работать", т.е., например, при выводе cout « v[0] вы, вероятно, увидите на экране I, как и ожидалось (и совершенно необоснованно!).

Но описанный сценарий - не более чем типичный вариант того, что может случиться. На самом деле все зависит от реализации стандартной библиотеки. Стандарт ничего не говорит о том, что должно происходить при записи элемента v[0] в пусто.м векторе v, поскольку программист легко может узнать о том, что вектор пуст, чтобы не пытаться выполнять такую запись. В конце концов, если ему очень надо, он может обеспечить выполнение соответствующей проверки, воспользовавшись вызовом v.atCO)...



0 1 [ 2 ] 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85