Обычно операторы присвоения объединяют действия деструктора и конструктора копий. Подобно деструктору, оператор присвоения освобождает ресурсы левого операнда. Подобно конструктору копий, оператор присвоения копирует данные из правого операнда. Однако критически важно, чтобы эти действия осуществлялись в правильной последовательности, даже если объект присваивается сам себе. Кроме того, по возможности следует писать собственные операторы присвоения так, чтобы они оставляли левый операнд в корректном состоянии, иначе произойдет исключение (см. раздел 5.6.2).
В данном случае можно отработать случай присвоения самому себе (и сделать код устойчивым к исключению), осуществляя сначала копирование правого операнда. После копирования освобождается левый операнд и указатель модифицируется так, чтобы он указывал на вновь зарезервированную строку:
HasPtr& HasPtr::operator=(const HasPtr &rhs) {
auto newp = new string(*rhs.ps); //
delete ps; //
ps = newp; //
i = rhs.i;
return *this; //
};
В этом операторе присвоения, безусловно, сначала выполняется работа конструктора: инициализатор newp идентичен инициализатору ps в конструкторе копий класса HasPtr. Затем, как в деструкторе, удаляется строка, на которую в настоящее время указывает указатель ps. Остается только скопировать указатель на недавно созданную строку и значение типа int из rhs в этот объект.
Создавая оператор присвоения, следует учитывать два момента.
• Операторы присвоения должны работать правильно, если объект присваивается сам себе.
• Большинство операторов присвоения делят работу с деструктором и конструктором копий.
Шаблон разработки оператора присвоения подразумевает сначала копирование правого операнда в локальный временный объект.
Для иллюстрации важности принятия мер против присвоения самому себе рассмотрим, что случилось бы, выгляди оператор присвоения так:
//
HasPtr&
HasPtr::operator=(const HasPtr &rhs) {
delete ps; //
//
//
ps = new string(*(rhs.ps));
i = rhs.i;
return *this;
}
Если rhs и этот объект совпадают, удаление ps освободит строку, на которую указывают и *this, и rhs. При попытке копирования *(rhs.ps) в операторе new этот указатель указывает уже на недопустимую область памяти. Результат непредсказуем.
Упражнение 13.23. Сравните функции-члены управления копированием, написанные для решения упражнений предыдущего раздела, с кодом, представленным здесь. Убедитесь, что понимаете различия, если таковые вообще есть, между вашим кодом и приведенным в книге.
Упражнение 13.24. Что будет, если в версии класса HasPtr данного раздела не определен деструктор? Что если не определен конструктор копий?