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_);
swap(buf_, msg.buf_);
}
int bufSize_;
int initBufSize_;
int msgSize_;
char* buf;
string key_;
}
Вся работа здесь делается конструктором копирования и закрытой функцией-членом swapInternals
. Конструктор копирования инициализирует в списке инициализации элементарные члены и один из неэлементарных членов. Затем он распределяет память для нового буфера и копирует туда данные. Довольно просто, но почему используется такая последовательность действий? Вы могли бы возразить, что всю инициализацию можно сделать в списке инициализации, но такой подход может сопровождаться тонкими ошибками.
Например, вы могли бы следующим образом выделить память под буфер в списке инициализации.
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_);
}
Вы можете ожидать, что все будет нормально, так как если завершается неудачей выполнение оператора new
, выделяющего память под буфер, все другие полностью сконструированные объекты будут уничтожены. Но это не гарантировано, потому что члены класса инициализируются в той последовательности, в которой они объявляются в заголовке класса, а не в порядке их перечисления в списке инициализации. Переменные-члены объявляются в следующем порядке.
int bufSize_;
int initBufSize_;
int msgSize_;
char* buf_;
string key_;
В результате buf_
будет инициализироваться перед key_
. Если при инициализации key_
будет выброшено исключение, buf_
не будет уничтожен, и у вас образуется участок недоступной памяти. От этого можно защититься путем использования в конструкторе блока try/catch
(см. рецепт 9.2), но проще разместить оператор инициализации buf_
в теле конструктора, что гарантирует его выполнение после операторов списка инициализации.