Выполнение функции 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
, которая принимает два аргумента
Поскольку объект key_
не является элементарным и поэтому операции над ним могут приводить к выбрасыванию исключений, я сначала обмениваю его значения. В этом случае, если выбрасывается исключение, никакие другие переменные-члены не будут испорчены. Однако это не значит, что не будет испорчен объект key_
. Когда вы работаете с членами объекта, все зависит от обеспечения ими гарантий безопасности при исключениях. Если такой член не выбрасывает исключение, то это значит, что я добился своего, так как обмен значений переменных встроенных типов не приведет к выбрасыванию исключений. Следовательно, функция swapInternals
является в основном и строгом смысле безопасной при исключениях.
Однако возникает интересный вопрос. Что, если у вас имеется несколько объектов-членов? Если бы вы имели два строковых члена, начало функции swapInternals
могло бы выглядеть следующим образом.
void swapInternals(Message& msg) {
swap(key_, msg key_);
swap(myObj_, msg.myObj_);
// ...
Существует одна проблема: если вторая операция swap
выбрасывает исключение, как можно безопасно отменить первую операцию swap
? Другими словами, теперь key_
имеет новое значение, но операция swap
для myObj_
завершилась неудачей, поэтому key_
теперь испорчен. Если вызывающая программа перехватывает исключение и попытается продолжить работу, как будто ничего не случилось, она теперь будет обрабатывать нечто отличное от того, что было в начале. Одно из решений — предварительно скопировать key_
во временную строку, но это не гарантирует безопасность, так как при копировании может быть выброшено исключение.
Одно из возможных решений состоит в использовании объектов, распределенных в динамической памяти.
void swapInternals(Message& msg) {