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

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

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

Сообщение Ghaydn » 16.03.2018 05:40:42

Не вариант. Не хочет ставить из-за зависимостей.
Upd. Из сырцов собрал. Совсем другое дело! И TPointF теперь есть, и AnchorDockingDsgn присутствует в списке доступных пакетов.
Ну всё, пойду дальше классы осваивать...
Ghaydn
незнакомец
 
Сообщения: 6
Зарегистрирован: 13.03.2018 13:26:24

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

Сообщение Cheb » 19.03.2018 11:33:12

Можно и самому с помощью RTTI написать,

Ея недостаточно, увы. Она слишком неполная. Я уже двенадцать лет решение пилю, пока ещё не готово. (Точнее, готово было в 2008-м, но криво. С тех пор переделываю).
Есть два пути: парсить исходники или обязательную регистрацию всех классов, где перечислять поля в специальном методе. Оба - для титанов духа.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

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

Сообщение Python » 08.04.2018 15:45:34

sign писал(а):
Ghaydn писал(а):2. Bar := Foo;

Bar, как уже было сказано выше, стал указателем на Foo
А Foo мы совсем потеряли, но сам экземпляр остался в памяти, но доступа к нему у Вас уже нет. Произошла утечка памяти.

Наоборот как раз. Bar мы потеряли, экземпляр остался в памяти. Если он создавался ранее, конечно. Ведь про это в вопросе ничего не сказано, так что может быть, никаких утечек и не будет.
Ghaydn писал(а):https://www.freepascal.org/docs-html/rtl/system/assign.html
Я чего-то не понимаю? Это же вообще для работы с файлами.

Нет, для работы с файлами AssignFile. Для копирования у потомков TPersistent - есть метод Assign. Правда, по умолчанию он ничего не делает (точнее, просто вызывает AssignTo, который порождает исключение). Чтобы получить от Assign какую-то пользу, надо перекрыть AssignTo.
Отличие от приведённого вами Clone заключается в том, что для того, чтобы работал Assign надо, чтобы принимающий объект был создан. То есть работает подход: "Выделил память - освободи". Получается очевидная вещь: сделал Create - сделай Free. В случае же с Clone для программиста может быть неочевидно, что он выделил память и ей надо сделать Free. Так что подход с Assign просто "идеологически более верный" и я не вижу смысла изобретать велосипед с clone, имеет смысл пользоваться именно Assign и перекрывать AssignTo.
Ghaydn писал(а):Record'ы этой беде не подвержены, их можно присваивать сколько влезет, каждый раз будет выделяться новая область памяти

Ни разу не будет. Область памяти будет одна и та же. Просто будет копироваться содержимое. Если внутри записи будут "сложные" типы данных типа объектов, или файлов - огребёте проблем ровно столько же, сколько при присваивании указателей.
TPersistent ИМХО изначально задумывался именно как хранитель реализации AssignTo и ряда других полезняшек типа SaveToStream, LoadFromStream и ряда других сходных методов с реализацией "по умолчанию" чисто через RTTI. То есть все published методы должны копироваться, записываться, считываться. Но, по видимому, Борландовцы (или кто они?) решили, что это может быть опасно из-за вот таких вот случаев (что делать, если published поле содержит TMemoryStream?), а потому решили не реализовывать вообще ничего, а по их образу и подобию решили сделать и FPCшники.
Cheb писал(а):Есть два пути: парсить исходники или обязательную регистрацию всех классов, где перечислять поля в специальном методе. Оба - для титанов духа.

Попытки решения есть, разной степени удачности, например:
http://delphikingdom.com/asp/viewitem.a ... logid=1212
Python
новенький
 
Сообщения: 20
Зарегистрирован: 23.01.2018 21:50:17

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

Сообщение olegy123 » 08.04.2018 16:32:26

Учтите, что сейчас в моде рандомное выделение памяти - класс и его части могут быть в разных местах находится.
olegy123
долгожитель
 
Сообщения: 1643
Зарегистрирован: 25.02.2016 12:10:20

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

Сообщение sign » 09.04.2018 08:12:11

Python писал(а):
sign писал(а):
Ghaydn писал(а):2. Bar := Foo;

Bar, как уже было сказано выше, стал указателем на Foo
А Foo мы совсем потеряли, но сам экземпляр остался в памяти, но доступа к нему у Вас уже нет. Произошла утечка памяти.

Наоборот как раз. Bar мы потеряли, экземпляр остался в памяти. Если он создавался ранее, конечно. Ведь про это в вопросе ничего не сказано, так что может быть, никаких утечек и не будет.
Ghaydn писал(а):https://www.freepascal.org/docs-html/rtl/system/assign.html
Да, перепутал. Мы ж Bar поменяли.

Python писал(а):Для копирования у потомков TPersistent - есть метод Assign. Правда, по умолчанию он ничего не делает (точнее, просто вызывает AssignTo, который порождает исключение). Чтобы получить от Assign какую-то пользу, надо перекрыть AssignTo.

Для перекрытия предусмотрен Assign.
Код: Выделить всё
 
TPersistent = class(TObject,IFPObserved)
...
public
   ...
   procedure Assign(Source: TPersistent); virtual;
sign
энтузиаст
 
Сообщения: 1131
Зарегистрирован: 30.08.2009 09:20:53

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

Сообщение MylnikovDm » 10.04.2018 12:53:58

Уважаемый Ghaydn, если эта тема ещё для вас актуальна, а также для всех остальных новичков, кому она может быть интересна.

Общий принцип работы с классами отличается от работы с обычными переменными или теми же "рекордами" тем, что это всегда работа с указателями на некую область памяти.
В более ранних версиях языка Pascal, когда появились "объекты", требовалось явно объявлять и писать соответствующие переменные и выражения как указатели. А когда в первой версии Delphi фирма Borland добавила в язык классы, то чтобы не писать постоянно лишние символы при работе с указателями, было решено, что других вариантов работы с классами не будет, а компилятор будет всегда воспринимать переменные классов как указатели и добавлять необходимые преобразования (типа, подставлять символ взятия указателя).

То есть, сейчас мы пишем просто bar.caption, а без этой фичи компилятора должны были бы везде писать bar^.caption.

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

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

Код: Выделить всё
procedure Test1;
var
  MyClass1: TMyClass;

begin
  MyClass := TMyClass.Create;  // тут мы создаём экземпляр класса, при этом выделяется необходимая область памяти, которая инициализируется
                                              // в конструкторе Create требуемыми значениями полей класса
  ...
  MyClass.Pole1 := ...              // различные действия с экземпляром класса
  ...

  MyClass.Free;                       // удаление экземпляра класса, освобождение занятой им памяти.

end;


В начале всегда вызываем конструктор, который в том числе выделяет необходимую память, потом работаем с полями и методами класса, а в конце обязательно вызываем Free для освобождения памяти.
Отдельно следует обратить внимание на то, что в классе мы описываем деструктор destroy, который при этом сами никогда не вызываем! Он будет вызван из метода Free.

Теперь рассматриваемая вами ситуация, когда нам понадобилась копия какого-то объекта. Общепринятый подход следующий. Сначала мы создаём два разных экземпляра класса, а уже затем используем метод, который все значения полей одного экземпляра копирует в поля второго экземпляра. В стандартной библиотеке такой метод обычно называется Assign.

Если же вы хотите себе чуть чуть упростить жизнь, то можно сделать функцию Clone, которая будет совмещать в себе создание нового экземпляра класса с копированием значений полей. В той библиотеке, которой пользуемся мы в своих разработках, подобная функция присутствует, но реализована она через использование метода Assign.

Пример:
Код: Выделить всё
type
  TFirst = class(TObject)
  public
    Pole1: integer;
    Pole2: string;
   
   constructor Create; virtual;

    procedure Assign(aSource: TFirst); virtual;
    function Clone(aSource: TFirst): TFirst;

    function MyClass: TFirstClass; inline;
  end;
 
  TFirstClass = class of TFirst;


  TSecond = class(TFisrt)
  public
    Pole3: TFisrt;
    Pole4: double;
   
    constructor Create; override;
    destructor Destroy; override;
   
    procedure Assign(aSource: TFirst); override;
  end;


implementation

{TFisrt}

constructor TFirst.Create;
begin
  Pole1 := 10;
  Pole2 := 'fisrt';
end;

procedure TFirst.Assign(aSource: TFirst);
begin
  Pole1 := aSource.Pole1;
  Pole2 := aSource.Pole2;
end;

function Clone(aSource: TFirst): TFirst;
begin
  Result := MyClass.Create;
  Result.Assign(aSource);
end;

function MyClass: TFirstClass;
begin
  Result := TFisrtClass(ClassType);
end;

{TSecond}

constructor TSecond.Create; override;
begin
  Inherited;
  Pole2 := 'second';
  Pole4 := 0.5;
  Pole3 := TFisrt.Create;
end;

destructor TSecond.Destroy; override;
begin
  Pole3.Free;
  Inherited;
end;
   
procedure TSecond.Assign(aSource: TFirst); override;
begin
  Inherited Assign(aSource);
  if aSource is TSecond then begin
    Pole3.Assign(TSecond(aSource).Pole3);
    Pole4 := TSecond(aSource).Pole4;
  end;
end;


Почему удобнее сделать именно через два разных метода Assign и Clone?
Потому, что могут быть ситуации, когда у вас уже имеется экземпляр класса и вам не нужно его заново создавать, но нужно его полям присвоить значения из какого-то другого объекта. При этом конструкцию типа:
Код: Выделить всё
  SecondCopy.Free;
  secondCopy := TSecond(aSource.Clone);

нельзя будет использовать, если у вас в разных местах есть множество переменных, которые указывают на данный объект. В результате у вас актуальной будет только значение той переменной, которая задействована в данном месте, а всё остальные после выполнения этого кода, будут иметь недопустимый указатель. Если же вы используете

SecondCopy.Assign(aSource);

То у вас не будет создан новый экземпляр объекта, а просто значениям полей SecondCopy будут присвоены значения из aSource. Область памяти при этом останется та же самая и все остальные ссылки на данный экземпляр объекта останутся рабочими.

Ещё одно замечание по поводу использования виртуального конструктора и MyClass в методе Clone. Несмотря на то, что везде в объявлениях использован TFirst, это будет работать со всеми классами наследниками от TFisrt! Для этого в методе MyClass специально использована конструкция TFisrtClass, которая означает класс TFisrt и всех его наследников.

Правда, в тех местах, когда вы будете использовать Clone для клонирования объектов наследников, придётся явно приводить тип к типу наследника, как это сделано в последнем примере.

Также следует отметить, что если использовать приведённые в примере схемы, то метод Assign может быть вызван между объектами разных классов-наследников из этой иерархии. То есть, если написать:

Second.Assign(First);

то метод сработает только для полей, которые есть в First, а значения остальных полей останутся прежними.

Если написать:

First.Assign(Second);

то все поля First будут заполнены значениями из Second.

В тех случаях, когда у вас сложная иерархия классов-наследников от TFisrt, присвоение полей будет происходить по ближайшему общему предку.
MylnikovDm
постоялец
 
Сообщения: 103
Зарегистрирован: 15.02.2007 21:26:10
Откуда: Челябинск

Пред.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2

Рейтинг@Mail.ru