этих программ впоследствии было переделано с учетом стандарта.
19.4.1.2. Эффективность
Да, проверка выхода за пределы диапазона в экстремальных случаях, таких как буферы сетевых интерфейсов и матрицы в высокопроизводительных научных вычислениях, может оказаться слишком сложной. Однако стоимость проверки выхода за пределы допустимого диапазона редко учитывается при обычных вычислениях, которые выполняются в большинстве случаев. Таким образом, мы рекомендуем при малейшей возможности использовать проверку выхода за пределы допустимого диапазона в классе vector.
19.4.1.3. Ограничения
В этом пункте, как и в предыдущем, аргументы нельзя считать универсальными. Несмотря на то что они разделяются практически всеми программистами и не могут быть просто отброшены, если вы начинаете писать новую программу в среде, не связанной с вычислениями в реальном времени (см. раздел 25.2.1), то используйте обработку ошибок с помощью исключений и векторы с проверкой выхода за пределы допустимого диапазона.
19.4.1.4. Необязательная проверка
Стандарт ISO C++ утверждает, что выход за пределы допустимого диапазона вектора не имеет гарантированной семантики, поэтому его следует избегать. В соответствии со стандартом при попытке выхода за пределы допустимого диапазона следует генерировать исключение. Следовательно, если вы хотите, чтобы класс vector генерировал исключения и не создавал проблем, связанных с первыми тремя аргументами, в конкретном приложении следует использовать класс vector с проверкой выхода за пределы допустимого диапазона. Именно этого принципа мы придерживаемся в нашей книге.
Короче говоря, реальная программа может оказаться сложнее, чем хотелось бы, но всегда есть возможность скопировать готовые решения.
19.4.2. Признание: макрос
Как и наш класс vector, большинство реализаций стандартного класса vector не гарантирует проверку выхода за пределы допустимого диапазона с помощью оператора индексирования ([]), а вместо этого содержит функцию at(), выполняющую такую проверку. В каком же месте нашей программы возникают исключения std::out_of_range? По существу, мы выбрали вариант 4 из раздела 19.4.1: реализация класса vector не обязана проверять выход за пределы допустимого диапазона с помощью оператора [], но ей не запрещено делать это иным способом, и мы решили воспользоваться этой возможностью. Однако в нашей отладочной версии под названием Vector, разрабатывая код, мы реализовали проверку в операторе []. Это позволяет сократить время отладки за счет небольшой потери производительности программы.
struct Range_error:out_of_range { // подробное сообщение
// о выходе за пределы допустимого диапазона
int index;
Range_error(int i):out_of_range("Range error"), index(i)
{ }
};
template<class T> struct Vector:public std::vector<T> {
typedef typename std::vector<T>::size_type size_type;
Vector() { }
explicit Vector(size_type n):std::vector<T>(n) {}
Vector(size_type n, const T& v):std::vector<T>(n,v) {}
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, предусматривающую систематическую проверку, — возможно, именно это нам и следовало сделать; у нас нет информации, насколько строгой является проверка, предусмотренная вашим компилятором и библиотекой (поскольку это выходит за рамки стандарта).
В заголовке 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, пишите сообщение об ошибке, а затем прекращайте выполнение программы” — слишком прост для большинства нетривиальных систем.
Один из фундаментальных принципов программирования заключается в том, что, если мы запрашиваем ресурс, то должны — явно или неявно — вернуть его системе. Перечислим ресурсы системы.
• Память (memory).
• Блокировки (locks).
• Дескрипторы файлов (file handles).
• Дескрипторы потоков (thread handles).
• Сокеты (sockets).
• Окна (windows).
По существу, ресурс — это нечто, что можно получить и необходимо вернуть (освободить) самостоятельно или по требованию менеджера ресурса. Простейшим примером ресурса является свободная память, которую мы занимаем, используя оператор new, и возвращаем с помощью оператора delete. Рассмотрим пример.
void suspicious(int s, int x)
{
int* p = new int[s]; // занимаем память
// ...
delete[] p; // освобождаем память
}
Как мы видели в разделе 17.4.6, следует помнить о необходимости освободить память, что не всегда просто выполнить. Исключения еще больше усугубляют ситуацию, и в результате из-за невежества или небрежности может возникнуть утечка ресурсов. В качестве примера рассмотрим функцию suspicious(), которая