Опубликован: 17.10.2005 | Уровень: специалист | Доступ: платный
Лекция 8:

Динамические структуры: объекты

Клонирование и сравнение объектов

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

Если y присоединено к объекту OY, выражение

clone (y)

означает создание нового объекта OX, такого, что он имеет те же поля, что и OY, и все соответствующие поля имеют идентичные значения. Если y равно void, то значение clone (y) также void.

Скопировать присоединенный к y объект и связать копию со ссылкой x позволяет присваивание:

[1]
x := clone (y)

Вот иллюстрация этого механизма:

Клонирование объекта

Рис. 8.15. Клонирование объекта

Наряду со сравнением ссылок необходим механизм, позволяющий сравнивать объекты. Этой цели служит функция equal. Вызов:

equal (x, y)

возвращает значение true, если и только если x и y оба имеют значение void или присоединены к двум объектам с идентичными полями. После выполнения присваивания с клонированием [1], состояние, непосредственно следующее за присваиванием, удовлетворяет equal (x, y).

Возможно, вы удивляетесь, почему у функции clone есть аргумент, а у функции equal - их два. Для ОО-стиля характерен квалифицируемый вызов в форме: y.twin и x.is_equal (y). Ответ появится в разделе обсуждения, но это будет еще не скоро, так что попытайтесь догадаться сами.

Копирование объектов

Функция clone создает новую копию существующего объекта. Иногда целевой объект уже существует, и все, что необходимо, это скопировать значения полей. Процедура copy выполняет эту работу. Она вызывается обычным образом:

x.copy (y)

Сущности x и y должны быть одного и того же типа; эффект от выполнения - копирование полей объекта, присоединенного к y, в соответствующие поля объекта, присоединенного к x.

Как и во всех вызовах компонента, вызов copy требует, чтобы целевой объект x был не пуст. Дополнительно требуется, чтобы и y был не пуст. Эта неспособность иметь дело с пустыми ссылками отличает copy от clone.

Требование не пустоты y настолько важно, что должен существовать способ для его формального выражения. Фактически речь идет о более общей проблеме: как программа может задать предусловия на аргументы, передаваемые клиентом при ее вызове. Такие предусловия, являясь частным случаем общего понятия "утверждение" в деталях будут обсуждаться в последующих лекциях. Аналогично, нам хотелось бы уметь выражать в виде постусловия семантическое свойство, отмеченное выше, - результат выполнения clone удовлетворяет equal.

Процедура copy может считаться более фундаментальной, чем функция clone в том смысле, что, по меньшей мере, для класса без процедуры создания можно выразить clone в терминах copy следующим образом:

clone (y: SOME_TYPE) is
         -- Void если y равно void; иначе дублировать присоединенный к y объект
   do
         if y /= Void then
            create Result --Правильно только в отсутствие процедур создания
            Result.copy (y)
         end
end

При вызове функции сущность Result автоматически инициализируется в соответствии с общими правилами для атрибутов. Вот почему нет необходимости в ветви else условного оператора: Result инициализируется значением Void, так что результатом функции будет void, если значение y это void.

Глубокое клонирование и сравнение

Формы копирования и сравнения, реализуемые подпрограммами clone, equal и copy, называются поверхностными, поскольку они работают с объектами только на первом уровне, никогда не пытаясь следовать вглубь по ссылкам. Возникает необходимость для глубоких вариантов этих операций, рекурсивно дублирующих полную структуру.

Для понимания разницы рассмотрим пример, показанный на рис.8.16. Предположим, что мы начинаем в начальном состоянии A, где сущность a присоединена к объекту O1.

Различные формы присваивания и клонирования

Рис. 8.16. Различные формы присваивания и клонирования

Рассмотрим простое присваивание ссылки:

b := a

В состоянии B, показанном на рисунке, цель b в результате присваивания присоединена к объекту O1, к которому присоединен источник a. Никаких новых объектов не создается.

Далее рассмотрим операцию клонирования:

c := clone (a)

Эта инструкция, как показывает раздел C нашего рисунка, создает новый объект O4, с полями, идентичными полям объекта O1. Будут скопированы два ссылочных поля, и значения ссылок будут указывать на те же объекты O1 и O3, как и поля оригинального объекта O1. Но, заметьте, не происходит дублирования самого объекта O3, и никакого другого объекта помимо дублирования O1. По этой причине базисная операция clone называется поверхностным клонированием, - она останавливается на первом уровне объектной структуры.

Заметьте, при клонировании исчезли ссылки на себя. Ссылка landlord объекта O1 была присоединена к самому объекту O1. У объекта O4 это поле становится ссылкой на оригинал O1.

В некоторых ситуациях вы, возможно, захотите пойти дальше и дублировать структуру рекурсивно без введения разделяемых ссылок. Функция глубокого клонирования deep_clone позволяет достичь цели. Процесс создания deep_clone (y) рекурсивно следует за всеми ссылочными полями, содержащимися в объекте, дублируя полную структуру. (Если y это void, то и результат будет также void.) Эта функция будет, конечно же, правильно обрабатывать циклические ссылочные структуры.

Нижняя часть на рисунке - раздел D - иллюстрирует выполнение этой операции:

d := deep_clone (a)

В этом случае не появляются новые разделяемые ссылки. Все объекты, прямо или косвенно доступные объекту O1, будут дублированы, создавая новые объекты O5, O6 и O7. Нет никаких связей между старыми объектами ( O1, O2 и O3 ) и новыми. Объект O5, дублирующий O1, имеет собственные ссылки на себя.

Так же, как необходимы операции глубокого и поверхностного клонирования, необходимо иметь глубокий вариант эквивалентности. Функция deep_equal сравнивает две объектные структуры, определяя их структурную идентичность. В примере, показанном на рисунке, deep_equal выполнимо для любой пары из a, b и d. В то же время equal (a, c) истинно, поскольку поля объектов O1 и O4 идентичны, equal (a, d) - ложно. Фактически equal не выполнимо ни для одной пары из d и любого элемента оставшейся тройки. В целом имеют место следующие свойства:

  • В результате присваивания x := clone (y) или вызова x.copy (y), выражение equal (x, y) имеет значение true (в случае присваивания это свойство имеет место независимо от того, имеет ли y значение void ).
  • В результате присваивания x := deep_clone (y), выражение deep_equal (x, y) имеет значение true.

Эти свойства будут отражены в постусловиях соответствующих подпрограмм.

Александр Шалухо
Александр Шалухо
Анатолий Садков
Анатолий Садков

При заказе pdf документа с сертификатом будет отправлен только сертификат или что-то ещё?

Алексей Щербанов
Алексей Щербанов
Россия, г. Оренбург
Ксения Маковецкая
Ксения Маковецкая
Россия, Москва, МГЮА им. О.Е. Кутафина, 2014