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_ в теле конструктора, что гарантирует его выполнение после операторов списка инициализации.

Выполнение функции copy не приведет к выбрасыванию исключения, так как она копирует элементарные значения. Но именно это место является тонким с точки зрения безопасности исключений: эта функция может выбросить исключение, если копируются объекты (например, если речь идет о контейнере, который параметризован типом своих элементов, T); в этом случае вам придется перехватывать исключение и освобождать связанную с ним память.

Вы можете поступить по-другому и копировать объект при помощи оператора присваивания, operator=. Поскольку этот оператор и конструктор копирования выполняют аналогичные действия (например, приравнивают члены моего класса к членам аргумента), воспользуйтесь тем, что вы уже сделали, и вы облегчите себе жизнь. Единственная особенность заключается в том, что вы можете сделать более привлекательным ваш программный код, используя закрытую функцию-член для обмена значений между данными-членами и временным объектом. Мне бы хотелось быть изобретателем этого приема, но я обязан отдать должное Гербу Саттеру (Herb Sutter) и Стефану Дьюхарсту (Stephen Dewhurst), в работе которых я впервые познакомился с этим подходом.

Возможно, вам все здесь ясно с первого взгляда, но я дам пояснения на тот случай, если это не так. Рассмотрим первую строку, в которой создается временный объект tmp с помощью конструктора копирования.

Message tmp(rhs);

В данном случае мы просто создали двойника объекта-аргумента. Естественно, теперь tmp эквивалентен rhs. После этого мы обмениваем значения его членов со значениями членов объекта *this.

swapInternals(tmp);

Вскоре я вернусь к функции swapInternals. В данный момент нам важно только то, что члены *this имеют значения, которые имели члены tmp секунду назад. Однако объект tmp представлял собой копию объекта rhs, поэтому теперь *this эквивалентен rhs. Но подождите: у нас по-прежнему имеется этот временный объект. Нет проблем, когда вы возвратите *this, tmp будет автоматически уничтожен вместе со старыми значениями переменных-членов при выходе за диапазон его видимости.

return(*this);

Все так. Но обеспечивает ли это безопасность при исключениях? Безопасно конструирование объекта tmp, поскольку наш конструктор является безопасным при исключениях. Большая часть работы выполняется функцией swapInternals, поэтому рассмотрим, что в ней делается, и безопасны ли эти действия при исключениях.

Функция swapInternals выполняет обмен значениями между каждым данным-членом текущего объекта и переданного ей объекта. Это делается с помощью функции swap, которая принимает два аргумента a и b, создает временную копию a, присваивает аргумент b аргументу а и затем присваивает временную копию аргументу b. В этом случае такие действия являются безопасными и нейтральными по отношению к исключениям, так как источником исключений здесь могут быть только объекты, над которыми выполняются операции. Здесь не используется динамическая память и поэтому обеспечивается базовая гарантия отсутствия утечки ресурсов.

Поскольку объект key_ не является элементарным и поэтому операции над ним могут приводить к выбрасыванию исключений, я сначала обмениваю его значения. В этом случае, если выбрасывается исключение, никакие другие переменные-члены не будут испорчены. Однако это не значит, что не будет испорчен объект key_. Когда вы работаете с членами объекта, все зависит от обеспечения ими гарантий безопасности при исключениях. Если такой член не выбрасывает исключение, то это значит, что я добился своего, так как обмен значений переменных встроенных типов не приведет к выбрасыванию исключений. Следовательно, функция swapInternals является в основном и строгом смысле безопасной при исключениях.

Однако возникает интересный вопрос. Что, если у вас имеется несколько объектов-членов? Если бы вы имели два строковых члена, начало функции swapInternals могло бы выглядеть следующим образом.

void swapInternals(Message& msg) {

 swap(key_, msg key_);

 swap(myObj_, msg.myObj_);

 // ...

Существует одна проблема: если вторая операция swap выбрасывает исключение, как можно безопасно отменить первую операцию swap? Другими словами, теперь key_ имеет новое значение, но операция swap для myObj_ завершилась неудачей, поэтому key_ теперь испорчен. Если вызывающая программа

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату