|
Программирование >> Разработка устойчивых систем
Безопасность исключений в главе 7 мы подробно рассмотрим контейнеры стандартной библиотеки, в том числе контейнер stack. В частности, вы увидите, что объявление функции рор() выглядит так: void рорО: Может показаться странным, что функция рор() не возвращает значение, а просто выталкивает верхний элемент из стека. Чтобы получить значение этого элемента, приходится вызывать top() перед рор(). Однако такое поведение объясняется вескими причинами, связанными с безопасностью исключений - одним из критически важных факторов при проектировании библиотек. Существует несколько уровней безопасности исключений, но важнее другое: безопасность исключений означает правильную семантику при возникновении исключений. Предположим, вы реализуете стек на базе динамического массива (назовем его data, а счетчик элементов - count), и пытаетесь написать функцию рор() так, чтобы она возвращала значение. Код такой функции рор() будет выглядеть примерно так: tempiate<class Т> Т stack<T>::рор() { if(count == 0) throw logic error( stack underflow ); else return data[--count]: Что произойдет, если копирующий конструктор, вызываемый для возвращаемого значения в последней строке, запустит исключение? Извлеченный элемент из-за исключения не возвращается, но счетчик count уже уменьшился, поэтому верхний элемент теряется навсегда! Проблема заключается в том, что функция пытается одновременно, во-первых, вернуть значение, во-вторых, изменить состояние стека. Лучше разделить эти операции на две разные функции класса, что и делается в стандартном классе stack (другими словами, соблюдается принцип связности - каждая функция решает одну четко сформулированную задачу). Код, безопасный по отношению к исключениям, оставляет объекты в логически целостном состоянии и не приводит к утечке ресурсов. Осторожность также потребуется при написании пользовательских операторов присваивания. В главе 12 первого тома было показано, что оператор = должен работать по следующей схеме: 1. Убедиться в том, что объект не присваивается сам себе. Если это происходит, перейти к шагу 6 (проверка выполняется исключительно с целью оптимизации). 2. Выделить новую память для переменных-указателей. 3. Скопировать данные из старой памяти в новую. 4. Освободить старую память. 5. Обновить состояние объекта, присвоив переменным-указателям новые указатели на блоки, выделенные из кучи. 6. Вернуть *this. Важно, чтобы состояние объекта не изменялось до того момента, когда все компоненты будут успешно созданы и инициализированы. Шаги 2 и 3 обычно оформляются в виде отдельной функции, которая часто называется с1опе(). В следующем примере это делается для luiacca, содержащего две переменные-указателя: theString и thelnts: : C01:SafeAssign.cpp Оператор =. безопасный по отношению к исключениям #include <1ostream> #include <new> Для std::bad alloc #1 nclude <cstring> using namespace std: Класс с двумя переменными, содержащими указатели на память в куче class HasPointers { Класс Handle для хранения данных struct MyData { const char* theString: const int* thelnts; size t numlnts: MyData(const char* pString. const int* pints. size t nints) : theString(pString), thelnts(plnts). numlnts(nlnts) {} } *theData; Манипулятор Функции clone и cleanup static MyData* clone(const char* otherString. const int* otherlnts, s1ze t nlnts){ char* newChars = new char[strlen(otherString)+l]; int* newlnts; try { newlnts = new int[nlnts]: } catch (bad alloc&) { delete [] newChars: throw; try { В данном примере используются встроенные типы, поэтому исключения не генерируются. Однако при использовании классов исключения возможны, поэтому блок try используется для демонстрационных целей (для чего и нужен пример!) strcpy(newChars. otherString); for (size t i = 0: i < nInts: ++i) newlnts[i] = otherInts[i]: } catch (...) { delete [] newlnts; delete [] newChars: throw: return new MyData(newChars, newlnts. nInts); static MyData* clone(const MyData* otherData) { return clone(otherData->theString. otherData->theInts. OtherData->numlnts): static void cleanup(const MyData* theData) { delete [] theData->theString: delete [] theData->theInts: delete theData: public: HasPointersCconst char* someString. const int* somelnts. size t numlnts) { theData = clone(someString. somelnts. numlnts): HasPointers(const HasPointersS source) { theData = clone(source.theData): HasPointersS operator=(const HasPointersS rhs) { if (this != &rhs) { MyData* newData = clone(rhs.theData->theStri ng. rhs.theData->thelnts. rhs.theData->numInts): cleanup(theData): theData = newData; return *this: -HasPointersO { cleanup(theData): friend ostreamS operator (ostream& os, const HasPointersS obj) { OS obj.theData->theString : : for (size t i = 0: i < obj.theData->numlnts: ++i) OS obj.theData->theInts[i] : return OS: int mainO { int someNums[] = {1. 2. 3. 4}: size t someCount = sizeof someNums / sizeof someNums[0]: int someMoreNums[] = {5. 6. 7}: size t someMoreCount = sizeof someMoreNums / sizeof someMoreNums[0]: HasPointers hlCHello . someNums. someCount); HasPointers h2( Goodbye . someMoreNums, someMoreCount); cout hi endl; Hello: 12 3 4 hi = h2; cout hi endl; Goodbye: 5 6 7 } III:- Для удобства HasPointers использует класс MyData как манипулятор для работы с указателями. Когда требуется выделить дополнительную память (в результате конструирования или присваивания), в конечном счете для решения SToii задачи вызывается первая функция clone. Если первый вызов оператора new завершается неудачей, автоматически генерируется исключение bad alloc. Если неудача происходит при втором выделении памяти (для thelnts), память theString необходимо освободить - для этого и нужен блок try, перехватывающий исключение bad alloc. Второй блок try в данном случае не принципиален, поскольку копируются только числа int и указатели (так что исключений не будет), но при любом копировании
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |