После успешного распределения памяти я могу начать обновление состояния объекта, копируя данные и обновляя значения переменных-членов.
copy(buf_, buf_+msgSize_, p);
copy(data, data+len, p+msgSize_);
msgSize_ += len;
bufSize_ = newBufSize;
delete[] buf_;
buf_ = p;
Ни одна из этих операций не может выбросить исключение, поэтому нам не о чем волноваться. (Это происходит только из-за того, что буфер представляет собой последовательность символов; дополнительные разъяснения вы найдете при обсуждении примера 9.5.)
Это простое решение и общая стратегия обеспечения строгой безопасности функций- членов при исключениях заключается в следующем: сначала выполняйте все то, что может выбрасывать исключения, затем, когда вся опасная работа окажется выполненной, глубоко вздохните и обновите состояние объекта. appendDataпросто использует временную переменную для хранения нового размера буфера. Это решает проблему, связанную с размером буфера, но обеспечит ли это на самом деле базовую гарантию отсутствия утечки ресурсов? Обеспечит, но с трудом
сорувызывает operator=для каждого элемента копируемой последовательности. В примере 9.4 каждый элемент имеет тип char, поэтому безопасность обеспечена, так как оператор присваивания одного символа другому не может выбросить никакого исключения. Но я сказал «обеспечит с трудом», потому что безопасность этого специального случая не должна создавать у вас впечатление о том, что причиной исключений никогда не может быть функция copy.
Предположим на секунду, что вместо «узкого» символьного буфера вам необходимо написать класс Message, который может содержать массив каких-то объектов. Вы могли бы представить его как шаблон класса, подобный представленному в примере 9.5.
Пример 9.5. Параметризованный класс сообщения
template
class MessageGeneric {
public:
MessageGeneric(int bufSize = DEFAULT_BUF_SIZE) :
bufSize_(bufSize), initBufSize_(bufSize), msgSize_(0), buf_(new T[bufSize]) {}
~MessageGeneric() {
delete[] buf_;
}
void appendData(int len, const data) {
if (msgSize_+len > MAX_SIZE) {
throw out of range("Data size exceeds maximum size.");
}
if (msgSize_+len > bufSize_) {
int newBufSize = bufSize_;
while ((newBufSize *= 2) < msgSize_+len);
T* p = new T[newBufSize];
copy(buf_, buf_+msgSize_, p); // Могут ли эти операторы
copy(data, data+len, p+msgSize_); // выбросить исключение?
msgSize_ += len;
bufSize_ = newBufSize;
delete[] buf_; // Освободить старый буфер и установить указатель на
buf_ = p; // новый буфер
} else {
copy(data, data+len, buf_+msgSize_);
msgSize_ += len;
}
}
// Скопировать данные в буфер вызывающей программы
int getData(int maxLen, T* data) {
if (maxLen < msgSize_) {
throw out of range("This data is too big for your buffer.");
}
copy(buf_, buf_+msgSize_, data);
return(msgSize_);
}
private:
MessageGeneric(const MessageGeneric& orig) {}
MessageGeneric& operator=(const MessageGeneric& rhs) {}
int bufSize_;
int initBufSize_;
int msgSize_;
T* buf_;
};
Теперь вам необходимо быть более осторожным, так как вы заранее не знаете тип целевого объекта. Например, разве можно быть уверенным, что оператор T::operator=не выбросит исключение? Нельзя, поэтому вам необходимо учесть такую возможность. Заключите вызовы функций копирования в блок try.
try {
copy(buf_, buf_+msgSize_, p);
copy(data, data+len, p+msgSize_);
} catch(...) {
// He имеет значения, какое исключение выбрасывается; все, что
delete[] p; // мне необходимо сделать - это подчистить за собой,
throw; // а затем повторно выбросить исключение.
}
Поскольку оператор catchс многоточием позволяет перехватывать любой тип исключения, пользователи вашего класса могут быть уверены, что при выбрасывании исключения оператором T::operator=вы его перехватите и сможете освободить динамическую память, которая была только что распределена.
Строго говоря, функция copyв действительности ничего не выбрасывает, но это делает оператор T::operator=. Это происходит из-за того, что функция copyи остальные алгоритмы стандартной библиотеки в целом являются нейтральными по отношению к исключениям; это значит, что при выбрасывании исключений во время выполнения каких-либо внутренних операторов это исключение будет передано вызывающей программе, а не будет обработано полностью (перехвачено в блоке catchбез повторного выбрасывания этого исключения). Это сохраняет возможность перехвата исключений в блоке catch, выполнения некоторой подчистки с последующим их повторным выбрасываний, но в конце концов все исключения, выброшенные в классе или функции стандартной библиотеки, дойдут до вызывающей программы.
Читать дальше