Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

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



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

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

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

struct Point {

int х; // первая координата

int у; // вторая координата

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

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

};

Наша абстракция Point – это пара координат (х,у). Предусмотрено два конструктора: один инициализирует точку нулевыми зна­чениями координат, а другой – некоторыми заданными значениями.

Теперь определим точку, отображаемую на экране дисплея (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, б):

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

Item4 = Item3;

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

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

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

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

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

Item2 = &Item1;

Item4 -> move (Item2 -> location());

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

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

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

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

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

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

class DisplayPoint {

...

Point * DPoint

...

};

...

DisplayPoint Item1;

...

DisplayPoint Item2(Item1);

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

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

DisplayPoint operator= (const DisplayPoint &);

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

Рис. 3.2 Поэлементное копирование

DisplayPoint Item5;

Item5 = Item1;

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

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

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

int operator == (Point&);

int operator!= (Point&);





Дата публикования: 2015-04-06; Прочитано: 393 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.007 с)...