Есть много путей, ведущих к возникновению этой проблемы. Для ее предотвращения и следует предусмотреть в операции присваивания проверку на присваивание самому себе. Она очень проста и выглядит всегда совершенно одинаково:
POINT& POINT::operator=(const POINT& rhs)
{
if(this == &rhs) return *this; // проверка на присваивание себе
else { X=rhs.X; Y=rhs.Y; } //то, что делает оператор полезного
return *this; // возврат ссылки на объект
}
Сейчас мы попробуем в ней разобраться, благо для всех операций присваивания проверка на присваивание себе со-вершенно одинакова. Оператор if(this == &rhs) проверяет, не совпадает ли аргумент с самим объектом. Указатель this со-держит адрес вызывающего объекта, &rhs читается как "адрес rhs".
Таким образом, сравниваются два адреса. Если они эквивалентны (==), то это один и тот же объект. В этом случае, в полном соответствии с требованием воз-врата ссылки на объект, просто возвращаем *this (заметьте, что в конце функции делается то же самое) и выходим из функции.
Вспомните, что this - это указатель. Значение указателя - это адрес. Чтобы получить значение указателя, его следует разыменовывать. Разыменование указателя выглядит так: *ptr. Указатель this разыменовывается точно так же: *this.
Помещая эти две строки в начале и в конце тела операции присваивания, мы уменьшаем вероятность возникновения утечек памяти из-за этой опера-ции. Если вы запомнили приведенный здесь синтаксис копирования и при-сваивания и, определяя новый класс, сразу будете определять и их тоже, то это уже полдела.
Зачем C++ требует определения этих функций-членов?
Язык C++ не слишком сильно ограничивает свободу программистов в методах разработки программного обеспечения. В частности, он не навязывает вам способы копирования и присваивания. Количество и разнообразие ситуаций, в которых происходит копирование объектов, удивительно велико. Для очень простых объектов, состоящих из одного-двух элементов, затраты на копирование незначительны, но для более сложных, таких как графический интерфейс пользователя или комплексные типы данных, оперирующие с динамической памятью, издержки на копирование существенно возрастают.
Во-первых, имейте в виду, что если вы не определите для нового класса конструктор копий, то C++ создаст его сам. Причина заключается в том, что компилятору самому может потребоваться возможность создания копий, значит, эти две функции обязаны быть определены.
Во-вторых, вам может потребоваться заблокировать копирование, либо вести подсчет ссылок, или еще что-нибудь. Если вы не создадите эти функции, то C++ создаст для них версии по умолчанию.
Создаваемые компилятором версии обеих этих функций не всегда будут вас удовлетворять. Версии компилятора выполняют буквальное, или поразряд-ное, копирование. В некоторых случаях это неразумно. Не пожалейте времени на изучение ситуаций, которые могут вам встретиться при разработке программ.
Вот лишь некоторые из бесчисленного множества возможных ситуаций, в которых происходит копирование:
POINT х;
POINT у(х); // Прямой вызов конструктора копий.
POINT х = у; // Выглядит как присваивание, но на самом деле
// вызывает конструктор копий. Почему? См. ниже.
POINT a, b;
a = b; // Вызов операции присваивания
POINT Foo(); // Возврат по значению, вызывает копирование
void Foo(POINT); // Передача по значению, создает копию
Во всех этих случаях выполняется копирование. В ходе выполняемой компилятором оптимизации могут появиться и другие варианты. Это та область, где знание действительно сила, способная помочь вам избежать утечек памяти.
В операторе типа POINT х = у; не вызывается операция присваивания класса POINT, хотя на первый взгляд выглядит это именно так. Причина состоит в том, что операция присваивания - это функция-член, а значит может быть вызвана только для уже существующих объектов, в то время как в этом фрагменте происходит создание нового объекта х.
Если объект создается в той же строке, в которой он выступает в качестве левостороннего аргумента, то вызывается конструктор. Строка
Х х = у; // вызов конструктора копий
эквивалентна строке
Х х(у); // вызов конструктора копий
БИКЮ , что совсем не то же самое, что
Х х, у;
х = у; // вызов операции присваивания
Вам следует понимать, что же на самом деле вызывается, когда и почему. Это одна из тех особенностей, благодаря которым C++ труднее и интерес-нее, чем С. В предыдущем разделе мы пришли к заключению, что не стоит определять операцию присваивания без конструктора копий и наоборот.
Следовательно, напрашивается вывод, что основные рекомендации для операции присваивания справедливы также и для конструктора копий.
На этом, пожалуй пока и остановимся. Небольшое резюме напоследок.
Если класс содержит указатели или ссылки, то скорее всего вам придется определять операцию присваивания и конструктор копий для этого класса самостоятельно, не полагаясь на компилятор. В противном случае можно спокойно использо-вать созданные компилятором присваивание и копирование, но при этом по-лезно упомянуть об этом в комментариях к классу.
Список литературы
P.KimmelUsingBorlandC++ 5 SpecialEdition перевод BHV - С.Петербург 1997
C++. Бархатный путь Марченко А.Л. Центр Информационных Технологий
www.citmgu.ru
Thinking in C++, 2nd ed. Volume 1 c2000 byBruceEckel