Не вариант. Не хочет ставить из-за зависимостей.
Upd. Из сырцов собрал. Совсем другое дело! И TPointF теперь есть, и AnchorDockingDsgn присутствует в списке доступных пакетов.
Ну всё, пойду дальше классы осваивать...
Клонирование объекта класса
Модератор: Модераторы
Можно и самому с помощью RTTI написать,
Ея недостаточно, увы. Она слишком неполная. Я уже двенадцать лет решение пилю, пока ещё не готово. (Точнее, готово было в 2008-м, но криво. С тех пор переделываю).
Есть два пути: парсить исходники или обязательную регистрацию всех классов, где перечислять поля в специальном методе. Оба - для титанов духа.
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 писал(а):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;-
MylnikovDm
- постоялец
- Сообщения: 103
- Зарегистрирован: 15.02.2007 20:26:10
- Откуда: Челябинск
Уважаемый Ghaydn, если эта тема ещё для вас актуальна, а также для всех остальных новичков, кому она может быть интересна.
Общий принцип работы с классами отличается от работы с обычными переменными или теми же "рекордами" тем, что это всегда работа с указателями на некую область памяти.
В более ранних версиях языка Pascal, когда появились "объекты", требовалось явно объявлять и писать соответствующие переменные и выражения как указатели. А когда в первой версии Delphi фирма Borland добавила в язык классы, то чтобы не писать постоянно лишние символы при работе с указателями, было решено, что других вариантов работы с классами не будет, а компилятор будет всегда воспринимать переменные классов как указатели и добавлять необходимые преобразования (типа, подставлять символ взятия указателя).
То есть, сейчас мы пишем просто bar.caption, а без этой фичи компилятора должны были бы везде писать bar^.caption.
Именно этот момент, который обычно плохо объясняется в документации и учебниках, вводит начинающих программистов в заблуждение.
А поскольку все переменные классов являются указателями, то общая схема работы с ними выглядит всегда следующим образом:
В начале всегда вызываем конструктор, который в том числе выделяет необходимую память, потом работаем с полями и методами класса, а в конце обязательно вызываем Free для освобождения памяти.
Отдельно следует обратить внимание на то, что в классе мы описываем деструктор destroy, который при этом сами никогда не вызываем! Он будет вызван из метода Free.
Теперь рассматриваемая вами ситуация, когда нам понадобилась копия какого-то объекта. Общепринятый подход следующий. Сначала мы создаём два разных экземпляра класса, а уже затем используем метод, который все значения полей одного экземпляра копирует в поля второго экземпляра. В стандартной библиотеке такой метод обычно называется Assign.
Если же вы хотите себе чуть чуть упростить жизнь, то можно сделать функцию Clone, которая будет совмещать в себе создание нового экземпляра класса с копированием значений полей. В той библиотеке, которой пользуемся мы в своих разработках, подобная функция присутствует, но реализована она через использование метода Assign.
Пример:
Почему удобнее сделать именно через два разных метода Assign и 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, присвоение полей будет происходить по ближайшему общему предку.
Общий принцип работы с классами отличается от работы с обычными переменными или теми же "рекордами" тем, что это всегда работа с указателями на некую область памяти.
В более ранних версиях языка 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, присвоение полей будет происходить по ближайшему общему предку.
