тоже становится присваиванием самому себе, если окажется, что px и py указывают на одно и то же.
Эти менее очевидные случаи присваивания себе являются результатом совмещения имен
class Base {...};
class Derived: public Base {...};
void doSomething(const Base rb, // rb и *pd могут быть одним и тем же
Derived * pd); // объектом
Если вы следуете правилам 13 и 14, то всегда пользуетесь объектами для управления ресурсами; следите за тем, чтобы управляющие объекты правильно вели себя при копировании. В таком случае операторы присваивания должны быть безопасны относительно присваивания самому себе. Если вы пытаетесь управлять ресурсами самостоятельно (а как же иначе, если вы пишете класс для управления ресурсами), то можете попасть в ловушку, нечаянно освободив ресурс до его использования. Например, предположим, что вы создали класс, который содержит указатель на динамически распределенный объект класса Bitmap:
class Bitmap {...};
class Widget {
...
private:
Bitmap *pb; // указатель на объект, размещенный в куче
};
Ниже приведена реализация оператора присваивания operator=, которая выглядит совершенно нормально, но становится опасной в случае выполнения присваивания самому себе (она также небезопасна с точки зрения исключений, но сейчас не об этом).
Widget
Widget::operator=(const Widget rhs) // небезопасная реализация operator=
{
delete pb; // прекратить использование текущего
// объекта Bitmap
pb = new Bitmap(*rhs.pb); // начать использование копии объекта
// Bitmap, указанной в правой части
return *this; // см. правило 10
}
Проблема состоит в том, что внутри operator= *this (чему присваивается значение) и rhs (что присваивается) могут оказаться одним и тем же объектом. Если это случится, то delete уничтожит не только Bitmap, принадлежащий текущему объекту, но и Bitmap, принадлежащий объекту в правой части. По завершении работы этой функции Widget, который не должен был бы измениться в процессе присваивания самому себе, содержит указатель на удаленный объект!
Традиционный способ предотвратить эту ошибку состоит в том, что нужно выполнить проверку совпадения в начале operator=:
Widget
Widget::operator=(const Widget rhs) // небезопасная реализация operator=
{
if(this == rhs) return *this; // проверка совпадения: если
// присваивание самому себе, то
// ничего не делать
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
Это решает проблему, но я уже упоминал, что предыдущая версия оператора присваивания была не только опасна в случае присваивания себе, но и небезопасна в смысле исключений, и последняя опасность остается актуальной во второй версии. В частности, если выражение «new Bitmap» вызовет исключение (либо по причине недостатка свободной памяти, либо исключение возбудит конструктор копирования Bitmap), то Widget также будет содержать указатель на несуществующий Bitmap. Такие указатели – источник неприятностей. Их нельзя безопасно удалить, их даже нельзя разыменовывать. А вот потратить массу времени на отладку, выясняя, откуда они взялись, – это можно.
К счастью, существует способ одновременно сделать operator= безопасным в смысле исключений и безопасным по части присваивания самому себе. Поэтому все чаще программисты не занимаются специально присваиванием самому себе, а сосредоточивают усилия на достижении безопасности в смысле исключений. В правиле 29 эта проблема рассмотрена детально, а сейчас достаточно упомянуть, что во многих случаях продуманная последовательность операторов присваивания может обеспечить безопасность в смысле исключений (а заодно безопасность присваивания самому себе) кода. Например, ниже мы просто не удаляем pb до тех пор, пока не скопируем то, на что он указывает:
Widget Widget::operator=(const Widget rhs)
{