Литмир - Электронная Библиотека
A
A

  T& operator[](size_type int i)  // rather than return at(i);

  {

    if (i<0||this–>size()<=i) throw Range_error(i);

    return std::vector<T>::operator[](i);

  }

  const T& operator[](size_type int i) const

  {

    if (i<0||this–>size()<=i) throw Range_error(i);

    return std::vector<T>::operator[](i);

  }

};

Мы используем класс

Range_error
, чтобы облегчить отладку операции индексирования. Оператор
typedef
вводит удобный синоним, который подробно описан в разделе 20.5.

Класс

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 В заголовке
std_lib_facilities.h
мы используем ужасный трюк (макроподстановку), указывая, что слово vector означает
Vector
.

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

// с проверкой выхода за пределы допустимого диапазона

#define vector Vector

Это значит, что там, где вы написали слово

vector
, компилятор увидит слово
Vector
. Этот трюк ужасен тем, что вы видите не тот код, который видит компилятор. В реальных программах макросы являются источником довольно большого количества запутанных ошибок (разделы 27.8 и A.17).

Мы сделали то же самое, чтобы реализовать проверку выхода за пределы допустимого диапазона для класса

string
.

К сожалению, не существует стандартного, переносимого и ясного способа реализовать проверку выхода за пределы допустимого диапазона с помощью операции

[]
в классе
vector []
. Однако эту проверку в классах
vector
и
string
можно реализовать намного точнее и полнее. Хотя обычно это связано с заменой реализации стандартной библиотеки, уточнением опций инсталляции или с вмешательством в код стандартной библиотеки. Ни одна из этих возможностей неприемлема для новичков, приступающих к программированию, поэтому мы использовали класс
string
из главы 2. 

19.5. Ресурсы и исключения

Таким образом, объект класса

vector
может генерировать исключения, и мы рекомендуем, чтобы, если функция не может выполнить требуемое действие, она генерировала исключение и передавала сообщение в вызывающий модуль (см. главу 5). Теперь настало время подумать, как написать код, обрабатывающий исключения, сгенерированные операторами класса
vector
и другими функциями. Наивный ответ — “для перехвата исключения используйте блок
try
, пишите сообщение об ошибке, а затем прекращайте выполнение программы” — слишком прост для большинства нетривиальных систем.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Один из фундаментальных принципов программирования заключается в том, что, если мы запрашиваем ресурс, то должны — явно или неявно — вернуть его системе. Перечислим ресурсы системы.

• Память (memory).

• Блокировки (locks).

• Дескрипторы файлов (file handles).

• Дескрипторы потоков (thread handles).

• Сокеты (sockets).

• Окна (windows).

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 По существу, ресурс — это нечто, что можно получить и необходимо вернуть (освободить) самостоятельно или по требованию менеджера ресурса. Простейшим примером ресурса является свободная память, которую мы занимаем, используя оператор
new
, и возвращаем с помощью оператора
delete
. Рассмотрим пример.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  // ...

  delete[] p;          // освобождаем память

}

Как мы видели в разделе 17.4.6, следует помнить о необходимости освободить память, что не всегда просто выполнить. Исключения еще больше усугубляют ситуацию, и в результате из-за невежества или небрежности может возникнуть утечка ресурсов. В качестве примера рассмотрим функцию

suspicious()
, которая использует оператор new явным образом и присваивает результирующий указатель на локальную переменную, создавая очень опасную ситуацию.

19.5.1. Потенциальные проблемы управления ресурсами

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Рассмотрим одну из опасностей, таящуюся в следующем, казалось бы, безвредном присваивании указателей:

int* p = new int[s]; // занимаем память

Она заключается в трудности проверки того, что данному оператору new соответствует оператор

delete
. В функции
suspicious()
есть инструкция
delete[] p
, которая могла бы освободить память, но представим себе несколько причин, по которым это может и не произойти. Какие инструкции можно было бы вставить в часть, отмеченную многоточием,
...
, чтобы вызвать утечку памяти? Примеры, которые мы подобрали для иллюстрации возникающих проблем, должны натолкнуть вас на размышления и вызвать подозрения относительно такого кода. Кроме того, благодаря этим примерам вы оцените простоту и мощь альтернативного решения.

Возможно, указатель

p
больше не ссылается на объект, который мы хотим уничтожить с помощью оператора
delete
.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

264
{"b":"847443","o":1}