Идентичность

Идентичность – это такое свойство объекта, которое отличает его от всех других объектов.

Источником ошибок в объектно-ориентированном програм­мировании является неумение отличать имя объекта от самого объекта.

Пример. Определим точку на плоскости.

struct Point {

int х, у; // координаты

Point(void); // конструктор по умолчанию (0,0)

Point(int xValue, int yValue); // конструктор

};

Теперь определим точку, отображаемую на экране дисплея (DisplayPoint). Ограничимся возможностями рисовать точку и перемещать ее по экрану, а также запрашивать ее положение. Мы записываем нашу абстракцию в виде следующего объявления на C++:

class DisplayPoint {

public:

DisplayPoint(); // конструктор по умолчанию (0,0)

DisplayPoint(const Point& location); // конструктор

~DisplayPoint(); // деструктор

void draw(); // рисует точку на экране

void move(const Point& location); // перемещает точку

Point location(); // возвращает координаты

...

};

Аргументы некоторых функций указаны с модификатором const. Он указывает, что значение объекта, передаваемого по ссылке или указателю, в функции не изменится. Литералы, константы и аргументы, требующие преобразования типа, можно передавать как const&-аргументы и нельзя – в качестве не const &-аргументов.

Объявим экземпляры класса DisplayPoint:

DisplayPoint Item1;

DisplayPoint * Item2 = new DisplayPoint(Point(75,75));

DisplayPoint * Item3 = new DisplayPoint(Point(100,100));

DisplayPoint * Item4 = 0;

При выполнении этих операторов возникают четыре имени и три разных объекта (рис. 3.1 а). В памяти будут отведены четыре места под имена Item1, Item2, Item3, Item4. При этом Item1 будет именем объекта клас­са DisplayPoint, а три других – указателями. Кроме того, лишь Item2 и Item3 будут на самом деле указывать на объекты класса. У объектов, на ко­торые указывают Item2 и Item3, к тому же нет имен, хотя на них можно ссылаться «разыменовывая» соответствующие указатели (например, *Item2). Поэтому мы можем сказать, что Item2 указывает на отдельный объект класса, на имя которого мы можем косвенно ссылаться через *Item2.

Рис. 3.1 Идентичность объектов

Уникальная иден­тичность каждого объекта сохраняется на все время его существования, даже если его внутреннее состояние изменилось. При этом имя объекта не обязательно сохраняется.

Рассмотрим результат выполнения следующих операторов (рис. 3.1, б):

Item1.move(Item2 -> location());

Item4 = Item3;

Item4 -> move(Point(38, 100));

Объект Item1 и объект, на который указывает Item2, теперь относятся к одной и той же точке экрана. Указатель Item4 стал указывать на тот же объект, что и Item3. Хотя объект Item1 и объект, на который указывает Item2, имеют одинаковое состояние, они остаются разными объектами. Кроме того, мы изменили состояние объекта *Item3, использовав его новое косвенное имя Item4.

Ситуацию, когда объект именуется более чем одним способом несколькими синонимичными именами, называют структурной зависимостью.

Структурная зависимость порождает в объектно-ориентированном про­граммировании много проблем. Трудность распознания побочных эффектов при действиях с синонимичными объектами часто приводит к утечкам памяти, непра­вильному доступу к памяти и, хуже того, непрогнозируемому изменению состояния. Рассмотрим результат выполнения следующих действий (рис. 3.1, в):

delete Item3;

Item2 = &Item1;

В первой строке мы уничтожили объект через указатель Item3, теперь значение ука­зателя Item4 оказывается бессмысленным. Эта ситуация называется висячей ссылкой.

Во второй строке создается синоним: Item2 указывает на тот же объект, что и Item1. К сожалению, при этом произошла утечка памяти: объект, на который первона­чально указывал Item2, не именуется ни прямо, ни косвенно и его идентичность потеряна.

В языках типа C++ такая память освобождается только тогда, когда завершается программа, создавшая объект. Такие утечки памяти могут вызвать и просто неудобство, и крупные сбои, особенно если про­грамма должна непрерывно работать длительное время. Представьте себе утечку памяти в программе управления спутником. Перезапуск компьютера на спутнике в нескольких миллионах километров от Земли очень неудобен.

Для создания нового объекта, имеющего то же состояние, что и у существующего, необходимо вызвать конструктор копирования, имеющий следующее описание:

DisplayPoint(const DisplayPoint &); // конструктор копирования

Отсутствие этого спе­циального конструктора вызывает копирующий конструктор, действующий по умолчанию, который копирует объект поэлементно. Это разумно не всегда. Когда объект содер­жит ссылки или указатели на другие объекты, такая операция приводит к созда­нию синонимов указателей на эти объекты.

Пример. Модифицируем описание класса DisplayPoint так, чтобы каждый его экземпляр содержал указатель на точку:

class DisplayPoint {

...

Point * DPoint

...

};

...

DisplayPoint Item1;

DisplayPoint Item2(Item1); // вызов конструктора копирования

Поэлементное копирование объекта Item1 приведет к тому, что указатели на агрегированные объекты типа Point у обоих объектов Item1 и Item2 будут указывать на один и тот же объект, содержащий местоположение отображаемой точки (рис. 3.2). Фактически, имеем структурную зависимость: оба объекта будут ответственны за отображение одной и той же точки. Этого ли мы хотели достичь?

Рис. 3.2. Результат поэлементного копирования

Присваивание – это тоже копирование, и в C++ его смысл мож­но изменять. Например, мы могли бы добавить в определение класса DisplayPoint следующую строку:

DisplayPoint operator=(const DisplayPoint &);

Теперь мы можем записать

DisplayPoint Item5;

Item5 = Item1;

Как и в случае копирующего конструктора, если оператор присва­ивания не переопределен явно, то по умолчанию объект копируется поэлементно.

Понятие идентичности тесно связано с вопросом равенства. Равенство можно понимать двумя способами. Во-первых, два имени могут обозначать один и тот же объект (Item1 и Item2 на рис. 3.1, в). Во-вторых, это может быть равенство со­стояний у двух разных объектов (Item1 и Item2 на рис. 3.1, б).

В С++ нет предопределенного оператора равенства, поэтому мы должны опре­делить равенство и неравенство, объявив эти операторы при описании:

int operator ==(DisplayPoint&) const;

int operator!=(DisplayPoint&) const;


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: