Строго говоря, функция copy
в действительности ничего не выбрасывает, но это делает оператор T::operator=
. Это происходит из-за того, что функция copy
и остальные алгоритмы стандартной библиотеки в целом являются нейтральными по отношению к исключениям; это значит, что при выбрасывании исключений во время выполнения каких-либо внутренних операторов это исключение будет передано вызывающей программе, а не будет обработано полностью (перехвачено в блоке catch
без повторного выбрасывания этого исключения). Это сохраняет возможность перехвата исключений в блоке catch
, выполнения некоторой подчистки с последующим их повторным выбрасываний, но в конце концов все исключения, выброшенные в классе или функции стандартной библиотеки, дойдут до вызывающей программы.
Создание безопасных при исключениях функций-членов — трудоемкая работа. Для этого вам необходимо выявить все места, где могут выбрасываться исключения, и убедиться, что вы правильно их обрабатываете. Когда исключение может выбрасываться? При любом вызове функции. Операторы для встроенных типов данных не могут выбрасывать исключения, а деструкторы
Наконец, как и для большинства других требований, предъявляемых к программному обеспечению, вам требуется обеспечить только тот уровень безопасности исключений, который вам необходим. Другими словами, если вы создаете диалоговый мастер по генерации веб-страниц, график вашей разработки, вероятно, не позволит провести необходимое исследование и тестирование обеспечения в нем строгой безопасности исключений. Так, для вашего заказчика может быть приемлемой ситуация, когда пользователи встречаются иногда с сообщением о неопределенной ошибке: «Неизвестная ошибка, аварийное завершение программы» («Unknown error, aborting»). С другой стороны, если вы создаете программное обеспечение для управления углом ротора вертолета, ваш заказчик, вероятно, будет настаивать на обеспечении более существенных гарантий безопасности, чем вывод сообщения «Неизвестная ошибка, аварийное завершение программы».
9.5. Безопасное копирование объекта
Требуется иметь безопасные при исключениях конструктор копирования и оператор присваивания базового класса.
Примените тактику, предложенную в рецепте 9.4, а именно сначала выполните все действия, которые могут выбрасывать исключения, и изменяйте состояние объектов с помощью операций, которые не могут выбрасывать исключения только после того, как будет завершена вся опасная работа. В примере 9.6 вновь представлен класс Message
, который на этот раз содержит определения оператора присваивания и конструктора копирования.
#include <iostream>
#include <string>
const static int DEFAULT_BUF_SIZE = 3;
const Static int MAX_SIZE = 4096;
class Message {
public:
Message(int bufSize = DEFAULT_BUF_SIZE) :
bufSize_(bufSize), initBufSize_(bufSize), msgSize_(0), key_('') {
buf_ = new char[bufSize]; // Примечание: теперь это делается в теле
// конструктора
}
~Message() {
delete[] buf_;
}
// Безопасный при исключениях конструктор копирования
Message(const Message& orig) :
bufSize_(orig.bufSize_), initBufSize_(orig.initBufSize_),
msgSize_(orig.msgSize_), key_(orig.key_) {
// Эта функция может выбросить исключение
buf_ = new char[orig.bufSize_]; // ...здесь может произойти то же
// самое
copy(orig.buf_, orig.buf_+msgSize_, buf_); // Здесь нет
}
// Безопасный при исключениях оператор присваивания использующий
// конструктор копирования
Message& operator=(const Message& rhs) {
Message tmp(rhs); // Копировать сообщение во временную переменную,
// используя конструктор копирования
swapInternals(tmp); // Обменять значения переменных-членов и членов
// временного объекта
return(*this); // После выхода переменная tmp уничтожается вместе
// с первоначальными данными
}
const char* data() {
return(buf_);
}
private:
void swapInternals(Messages msg) {
// Поскольку key_ не является встроенным типом данных, он может
// выбрасывать исключение, поэтому сначала выполняем действия с ним
swap(key_, msg.key_);
// Если предыдущий оператор не выбрасывает исключение, то выполняем
// действия со всеми переменными-членами, которые являются встроенными
// типами
swap(bufSize_, msg.bufSize_);
swap(initBufSize_, msg.initBufSize_);
swap(msgSize_, msg.msgSize_);