Смекни!
smekni.com

Классы: копирование и присваивание (стр. 2 из 2)

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

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