![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Допустим (а такое таки иногда случается), у нас есть ряд классов, (как это обычно и бывает) потомков одного класса, для которых удобно делать вещи в духе
То есть иметь конструктор, в котором можно проинициализировать состояние объекта. Причём объект у нас сложный и может включать другие объекты. Которые, в свою очередь, могут включать третьи, и так далее. И вся эта радость создаётся в одном операторе. Удобно.
Но при таком подходе возникает проблема: а как быть с захламлением памяти? Ведь если в Java, например, есть GC, и он весь этот мусор, который появится после Entity.Free, то в Delphi (и в других old-school языках программирования, например С++) его нет, и мусор останется. При постоянном использовании подобного вида конструкторов получится неслабая такая утечка памяти.
Выход из такого положения очевиден: удалять все подобъекты при удалении корневого объекта. (Замечание в сторону: Тут, кстати, возникает следующая посторонняя проблема: подобъекты ведь могут как создаваться при создании объекта, так и быть ранее, а в конструктор передаваться лишь ссылки на них. Выходов из этой проблемы несколько: соглашение о том, что для этого объекта подобъекты создаются отдельно и нигде больше не используются; хранение флага принадлежности подобъекта к объекту, и при удалении, если этот флаг установлен, то подобъект надо удалить, иначе нет; и многие другие способы. Чаще всего используется reference counter в том или ином виде; при появлении новой ссылки на объект RefCounter увеличивается, при удалении уменьшается; когда он становится равен 0, объект убивает себя об /dev/null) Но писать отдельный деструктор для каждого нового класса обычно муторно и накладно (как бы не забыть об удалении всех подобъектов у каждого, а у каждого подобъекта могут быть ведь свои подобъекты и так далее).
Одним из возможных достаточно универсальных решений является использование RTTI. Вот, есть у нас класс TSmartDestroyableObject, у которого деструктор написан таким образом, что вызывает деструктор для всех классов-потомки TSmartDestroyableObject, которые может найти внутри себя. Собственно, деструктор выглядит так:
Тут комментарии на русском, но это исключительно для читабельности (и из-за того, что с английским я дружу плохо). Ни в коем случае не используйте неанглийские комментарии в коде.
Механизм действия сего деструктора (собственно, он уже описан выше): получаем список property, проходимся по всем, для классов вызываем Free. Если подобъекты являются потомками , и деструктор у них не переопределён (точнее, в нём вызывается деструктор для класса TSmartDestroyableObject), то они у себя сделают то же самое. И тогда, если у нас будут использоваться только объекты-потомки класса TSmartDestroyableObject, то мы сможем спокойно создавать целые их деревья одним оператором и потом спокойно удалять вызовом всего лишь одного метода Free для корневого объекта.
Ограничения: подобъекты должны быть объявены как published property объекта. Для корректного удаления целых деревьев объектов все нелистовые объекты должны быть потомками TSmartDestroyableObject.
Собственно, мне это понадобилось потому, что я таким образом создаю сообщения, которые надо сериализовать в поток. Потом подумал, что это будет полезно не только мне, и решил написать об этом.
В следующий раз будет что-нибудь про XML (Save/Load объектов, загрузка UI) и/или версионинг, смотря что доведу до презентабельного состояния первым.
Entity := TEntity.Create(TSubEntity1.Create(...), TSubEntity2.Create(...), ...);
...
Entity.Free;
...
Entity.Free;
То есть иметь конструктор, в котором можно проинициализировать состояние объекта. Причём объект у нас сложный и может включать другие объекты. Которые, в свою очередь, могут включать третьи, и так далее. И вся эта радость создаётся в одном операторе. Удобно.
Но при таком подходе возникает проблема: а как быть с захламлением памяти? Ведь если в Java, например, есть GC, и он весь этот мусор, который появится после Entity.Free, то в Delphi (и в других old-school языках программирования, например С++) его нет, и мусор останется. При постоянном использовании подобного вида конструкторов получится неслабая такая утечка памяти.
Выход из такого положения очевиден: удалять все подобъекты при удалении корневого объекта. (Замечание в сторону: Тут, кстати, возникает следующая посторонняя проблема: подобъекты ведь могут как создаваться при создании объекта, так и быть ранее, а в конструктор передаваться лишь ссылки на них. Выходов из этой проблемы несколько: соглашение о том, что для этого объекта подобъекты создаются отдельно и нигде больше не используются; хранение флага принадлежности подобъекта к объекту, и при удалении, если этот флаг установлен, то подобъект надо удалить, иначе нет; и многие другие способы. Чаще всего используется reference counter в том или ином виде; при появлении новой ссылки на объект RefCounter увеличивается, при удалении уменьшается; когда он становится равен 0, объект убивает себя об /dev/null) Но писать отдельный деструктор для каждого нового класса обычно муторно и накладно (как бы не забыть об удалении всех подобъектов у каждого, а у каждого подобъекта могут быть ведь свои подобъекты и так далее).
Одним из возможных достаточно универсальных решений является использование RTTI. Вот, есть у нас класс TSmartDestroyableObject, у которого деструктор написан таким образом, что вызывает деструктор для всех классов-потомки TSmartDestroyableObject, которые может найти внутри себя. Собственно, деструктор выглядит так:
uses TypInfo;
...
destructor TSmartDestroyableObject.Destroy;
var
I, Count: Integer;
PropList: PPropList; //для хранения списка property
SubObject : TObject;
begin
//Получение количества property для выделение места под хранение полученной о них информации
Count := GetTypeData(Self.ClassInfo)^.PropCount;
if Count > 0 then
begin
GetMem(PropList, Count * SizeOf(Pointer));
try
//Получение информации о property.
//Первый параметр — Class Info интересующего класса,
//второй — указатель на кусок памяти, по которому информация будет размещена.
//На самом деле, по переданному указателю размещаются только указатели на информацию,
//поэтому их ещё разыменовывать надо
GetPropInfos(Self.ClassInfo, PropList);
for I := 0 to Count - 1 do
begin
//Проверка, является ли текущая property классом
if (PropList^[I].PropType^.Kind = tkClass) then
begin
//Если является, то получаем ссылку на объект
SubObject := GetObjectProp(Self, PropList^[I]);
//И вызываем Free для него (Free вызывет Destroy,
//если ссылка на объект не nil).
SubObject.Free;
end;
end;
finally
FreeMem(PropList, Count * SizeOf(Pointer));
end;
end;
inherited;
end;
Тут комментарии на русском, но это исключительно для читабельности (и из-за того, что с английским я дружу плохо). Ни в коем случае не используйте неанглийские комментарии в коде.
Механизм действия сего деструктора (собственно, он уже описан выше): получаем список property, проходимся по всем, для классов вызываем Free. Если подобъекты являются потомками , и деструктор у них не переопределён (точнее, в нём вызывается деструктор для класса TSmartDestroyableObject), то они у себя сделают то же самое. И тогда, если у нас будут использоваться только объекты-потомки класса TSmartDestroyableObject, то мы сможем спокойно создавать целые их деревья одним оператором и потом спокойно удалять вызовом всего лишь одного метода Free для корневого объекта.
Ограничения: подобъекты должны быть объявены как published property объекта. Для корректного удаления целых деревьев объектов все нелистовые объекты должны быть потомками TSmartDestroyableObject.
Собственно, мне это понадобилось потому, что я таким образом создаю сообщения, которые надо сериализовать в поток. Потом подумал, что это будет полезно не только мне, и решил написать об этом.
В следующий раз будет что-нибудь про XML (Save/Load объектов, загрузка UI) и/или версионинг, смотря что доведу до презентабельного состояния первым.
no subject
Date: 2007-08-22 05:45 am (UTC)А переложить создание деструктора на рантайм -- менее муторно конечно, но производительность упадет. Ну ты и сам понимаешь.
Тэг-то последний какой пафосный, ойоо.
no subject
Date: 2007-08-22 07:43 am (UTC)Упадёт. Всё есть компромисс. Между hardcode и Самым Общим и Универсальным Случаем.
Угу. Я вообще пафосный весь из себя. На самом деле, возникло желание протегить все посты, где многобукф им, чтобы потом легче искать было. Если у тебя есть варианты, как назвать его иначе, я рассмотрю их.
no subject
Date: 2007-08-22 08:17 am (UTC)no subject
Date: 2007-08-22 08:20 am (UTC)no subject
Date: 2007-08-22 10:03 am (UTC)no subject
Date: 2007-08-22 05:43 pm (UTC)no subject
Date: 2007-08-22 06:09 pm (UTC)Так что у Дельфи принцип — «работает — не трожь», у С++ — убить всё, лишь бы мусора не осталось.
no subject
Date: 2007-08-22 05:48 pm (UTC)no subject
Date: 2007-08-22 12:28 pm (UTC)no subject
Date: 2007-08-22 05:44 pm (UTC)no subject
Date: 2007-08-22 07:41 am (UTC)C++ ни разу не school
no subject
Date: 2007-08-22 07:55 am (UTC)Да, кстати, http://en.wikipedia.org/wiki/Old_school , ага.
no subject
Date: 2007-08-22 08:15 am (UTC)no subject
Date: 2007-08-22 08:20 am (UTC)no subject
Date: 2007-08-22 08:23 am (UTC)no subject
Date: 2007-08-22 08:39 am (UTC)Разве что принудительно вызвать System.gc() (или как его там, не помню)
no subject
Date: 2007-08-22 10:05 am (UTC)Утечка памяти или хэндлов файлов в языке с GC - очень легко.
Например, добавляем что-то в контейнер (обработчики событий например), и забываем удалять. Типичный мемори-лик.
Наоткрываем тыщу файлов или создатим тыщу текстур - а GC про файлы или про тестуры ничего не знает, и не собирается.
no subject
Date: 2007-08-22 10:22 am (UTC)Ну наоткрывали, ну насоздавали. Что дальше? Как только исчезают все ссылки на эти объекты, они убиваются GC.
no subject
Date: 2007-08-22 09:26 pm (UTC)Спать мне надо больше!
Date: 2007-08-22 09:28 pm (UTC)no subject
Date: 2007-08-22 10:31 pm (UTC)Кстати, вроде шарпы (не знаю, какой версии) Головин ругал за то, что там берёшь, открываешь картинку, объект теряешь, а потом открыть тот же самый файл не можешь, ибо дескриптор висит. И закроется он только с очередным GC. Который будет неизвестно когда (или вообще никогда).
no subject
Date: 2007-08-23 09:09 am (UTC)В C#, к сведению Головина, есть нормальные деструкторы. Да и закрыть файл ручками никто не мешает. Странно, короче.
no subject
Date: 2007-08-23 09:20 am (UTC)no subject
Date: 2007-08-23 10:06 am (UTC)А где Головин прав? Если класс "благодаря" своей конструкции не может освободить критические ресурсы в любой момент, это вина разработчиков класса, а не языка. В стандартной библиотеке C# таких классов нет. А если программист сам теряет ссылку на объект раньше, чем выполняет его закрытие, то он ССЗБ и пусть теперь ждёт GC. Ибо на дураков, как известно, нормальные языки программирования не рассчитывают))) Так что C# тут ни при чём.
no subject
Date: 2007-08-23 11:37 am (UTC)no subject
Date: 2007-08-22 10:24 am (UTC)no subject
Date: 2007-08-22 10:25 am (UTC)no subject
Date: 2007-08-22 10:26 am (UTC)no subject
Date: 2007-08-22 12:27 pm (UTC)no subject
Date: 2007-08-22 08:48 am (UTC)no subject
Date: 2007-08-22 09:23 pm (UTC)A a = new A ( 5 );
то среда выполнения нам сама выделила память и при выходе из области видимости, в которой определён
a
, она эту память просто заберёт себе. Т. е. a умрёт без принудительных вызовов delete, и утечек не произойдёт.А если мы ручками открыли поток, то ручками должны его закрыть. GC за нас этого делать не должен (разве что создатель класса потока заставил его это делать).
no subject
Date: 2007-08-22 09:31 pm (UTC)no subject
Date: 2007-08-22 09:44 pm (UTC)Агрессивность сборщика мусора можно настраивать, если об этом речь.
no subject
Date: 2007-08-22 09:56 am (UTC)no subject
Date: 2007-08-22 09:59 am (UTC)no subject
Date: 2007-08-22 12:15 pm (UTC)no subject
Date: 2007-08-22 12:26 pm (UTC)