esyr: (Default)
[personal profile] esyr
Допустим (а такое таки иногда случается), у нас есть ряд классов, (как это обычно и бывает) потомков одного класса, для которых удобно делать вещи в духе
Entity := TEntity.Create(TSubEntity1.Create(...), TSubEntity2.Create(...), ...);
...
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) и/или версионинг, смотря что доведу до презентабельного состояния первым.

Date: 2007-08-22 05:45 am (UTC)
From: [identity profile] pourtous.livejournal.com
"и в других old-school языках программирования, например С++) его нет, и мусор останется" -- людям, не пишущим нормальные деструкторы, надо руки отрывать.

А переложить создание деструктора на рантайм -- менее муторно конечно, но производительность упадет. Ну ты и сам понимаешь.

Тэг-то последний какой пафосный, ойоо.

Date: 2007-08-22 07:43 am (UTC)
From: [identity profile] esyr.livejournal.com
Писать нормальные деструкторы для десятка (или сотни) классов, отличающиеся исключительно количественно, довольно муторно. Ага.

Упадёт. Всё есть компромисс. Между hardcode и Самым Общим и Универсальным Случаем.

Угу. Я вообще пафосный весь из себя. На самом деле, возникло желание протегить все посты, где многобукф им, чтобы потом легче искать было. Если у тебя есть варианты, как назвать его иначе, я рассмотрю их.

Date: 2007-08-22 08:20 am (UTC)
From: [identity profile] esyr.livejournal.com
Ы, а мне нравится.

Date: 2007-08-22 10:03 am (UTC)
From: [identity profile] http://users.livejournal.com/_winnie/
Тем, кто пишет деструкторы у сотни классов - надо оторвать руки. Деструкторы типично пишутся у десятка самых базовых кирпичиков вроде std::auto_ptr, boost::shared_ptr, std::vector, ...., а потом из этих кирпичиков собираются сотни классов с "пустыми" деструкторами, которые на самом деле генерируются автоматически.

class A {};

class B
{
   std::auto_ptr<A> p1;
   std::auto_ptr<A> p2;
   std::auto_ptr<boost::shared_ptr<A> > array;
};

Date: 2007-08-22 05:43 pm (UTC)
From: [identity profile] pourtous.livejournal.com
Но мусора то в таком случае не остается. В отличие от того что утверждается в многобукфе.

Date: 2007-08-22 06:09 pm (UTC)
From: [identity profile] esyr.livejournal.com
У автоматических деструкторов С++ (в Delphi их нет, так что тут наезды необоснованы, ага (а ведь сейчас начнёт орать о том, что Дельфи ущербна, я ведь знаю)) есть недостаток — они вызываются даже тогда, когда объект не надо уничтожать (ибо он ещё где используется). Али я что-то путаю?

Так что у Дельфи принцип — «работает — не трожь», у С++ — убить всё, лишь бы мусора не осталось.

Date: 2007-08-22 05:48 pm (UTC)
From: [identity profile] pourtous.livejournal.com
//Согласна с недостаточной точностью своей первой формулировки. Не следящим за тем чтобы деструкторы так или иначе у всех кто выделяет память были.

Date: 2007-08-22 12:28 pm (UTC)
From: [identity profile] esyr.livejournal.com
Так лучше, о луноликая бдительница пафоса?

Date: 2007-08-22 05:44 pm (UTC)
From: [identity profile] pourtous.livejournal.com
Хотела предложить FAQ, но многобукв тоже хорошо.

Date: 2007-08-22 07:41 am (UTC)
From: [identity profile] demetr.livejournal.com
>>и в других old-school языках программирования, например С++

C++ ни разу не school

Date: 2007-08-22 07:55 am (UTC)
From: [identity profile] esyr.livejournal.com
Не, C++ конечно не такой древний, как Алгол или Фортран, но тут имелось в виду, что это не новомодный язык из разряда Java/C#/PHP/Python с GC на борту и референциальной системой типов. Так что в этом смысле вполне себе олдскульный язык.

Да, кстати, http://en.wikipedia.org/wiki/Old_school , ага.

Date: 2007-08-22 08:15 am (UTC)
From: [identity profile] netp-npokon.livejournal.com
Где ж ему, болезному, взять GC, когда он низкоуровневый?)

Date: 2007-08-22 08:20 am (UTC)
From: [identity profile] esyr.livejournal.com
GC для C++ есть, это исключительно вопрос соглашений/компромиссов. Только не как часть языка, а как совершенно левая библиотека.

Date: 2007-08-22 08:23 am (UTC)
From: [identity profile] netp-npokon.livejournal.com
А что, Java-программеры правда полагаются на то, что кто-то придет и мусор уберет, btw?

Date: 2007-08-22 08:39 am (UTC)
From: [identity profile] esyr.livejournal.com
А у них есть выбор? :)

Разве что принудительно вызвать System.gc() (или как его там, не помню)

Date: 2007-08-22 10:05 am (UTC)
From: [identity profile] http://users.livejournal.com/_winnie/
Не полагаются.

Утечка памяти или хэндлов файлов в языке с GC - очень легко.
Например, добавляем что-то в контейнер (обработчики событий например), и забываем удалять. Типичный мемори-лик.

Наоткрываем тыщу файлов или создатим тыщу текстур - а GC про файлы или про тестуры ничего не знает, и не собирается.

Date: 2007-08-22 10:22 am (UTC)
From: [identity profile] esyr.livejournal.com
И? Пока он в контейнере — на него есть ссылка — он нужен (с точки зрения GC). Если контейнер прибить, то GC всё почистит.

Ну наоткрывали, ну насоздавали. Что дальше? Как только исчезают все ссылки на эти объекты, они убиваются GC.

Date: 2007-08-22 09:26 pm (UTC)
From: [identity profile] ximaera.livejournal.com
Как GC убъёт открытый файл? Он убъёт ссылку на него и идентификатор. Сам файл останется открытым - теперь уже навечно. Ведь GC в общем случае не знает, что с данным типом, помимо освобождения занимаемой памяти, надо делать что-то ещё (сообщать ОС, что файл не используется, в данном примере). Вот он ничего и не сделает.

Date: 2007-08-22 10:31 pm (UTC)
From: [identity profile] esyr.livejournal.com
Для этого вроде finalize есть. Чтобы рассказать GC, что делать. Точнее, кошерно всё сделать самому (бо GC торкает finalize для объекта при его, finalize, наличии перед его, объекта, убиением). Другое дело, что висеть оно может сколь угодно долго (пока память не припрёт), и закончится действительно может раньше памяти. Но в этом случае по идее должен спасать принудительный вызов GC. Как-то так.

Кстати, вроде шарпы (не знаю, какой версии) Головин ругал за то, что там берёшь, открываешь картинку, объект теряешь, а потом открыть тот же самый файл не можешь, ибо дескриптор висит. И закроется он только с очередным GC. Который будет неизвестно когда (или вообще никогда).

Date: 2007-08-23 09:09 am (UTC)
From: [identity profile] ximaera.livejournal.com
Проще и безопаснее закрывать критичные ресурсы самому, чем полагаться на GC.

В C#, к сведению Головина, есть нормальные деструкторы. Да и закрыть файл ручками никто не мешает. Странно, короче.

Date: 2007-08-23 09:20 am (UTC)
From: [identity profile] esyr.livejournal.com
Погуглил (http://www.google.ru/search?q=destructor+c%23), отсюда (http://www.ondotnet.com/pub/a/dotnet/2002/02/11/csharp_traps.html) и отсюда (http://www.codeproject.com/csharp/DestructorsInCs.asp) узнал, что «Destructors cannot be called. They are invoked automatically», причём это automatically «will be called by the garbage collector». И, где? Чем оно отличаетсяот finalize в Жабе? И где они нормальные? И где я раньше ошибся? И где Голвин неправ? То, что unmanaged ресурсы могут ручками закрываться, это да. Но если класс устроен так, что они закрываются только в деструкторе, то собственно вот.

Date: 2007-08-23 10:06 am (UTC)
From: [identity profile] ximaera.livejournal.com
По крайней мере, они зрительно связаны с конструктором и после выполнения автоматически вызывают деструктор базового класса. Для явного удаления объекта существуют интерфейс IDisposable и метод Dispose.

А где Головин прав? Если класс "благодаря" своей конструкции не может освободить критические ресурсы в любой момент, это вина разработчиков класса, а не языка. В стандартной библиотеке C# таких классов нет. А если программист сам теряет ссылку на объект раньше, чем выполняет его закрытие, то он ССЗБ и пусть теперь ждёт GC. Ибо на дураков, как известно, нормальные языки программирования не рассчитывают))) Так что C# тут ни при чём.

Date: 2007-08-23 11:37 am (UTC)
From: [identity profile] esyr.livejournal.com
Головин рассказывал про класс из стандартной библиотеки.

Date: 2007-08-22 10:24 am (UTC)
From: [identity profile] esyr.livejournal.com
Да, подсказка: в Java нельзя удалить объект ручками. Просто никак нельзя. Вот так. Убить его может только gc, когда придёт его время.

Date: 2007-08-22 10:25 am (UTC)
From: [identity profile] esyr.livejournal.com
Имеется в виду, что штатными средствами и всё такое. Хакинг JVM не рассматривается.

Date: 2007-08-22 10:26 am (UTC)
From: [identity profile] http://users.livejournal.com/_winnie/
Я хотел сказать, что наличе GC не спасает от утечек памяти.

Date: 2007-08-22 12:27 pm (UTC)
From: [identity profile] esyr.livejournal.com
Мы разные вещи понимаем под утечкой памяти. Точнее, ты понимаешь шире.

Date: 2007-08-22 08:48 am (UTC)
From: [identity profile] esyr.livejournal.com
Да, а разве Python-программеры полагаются не на то же самое? :)

Date: 2007-08-22 09:23 pm (UTC)
From: [identity profile] ximaera.livejournal.com
Мусор должен быть нерукотворный. Т. е. если мы сделали
A a = new A ( 5 );
то среда выполнения нам сама выделила память и при выходе из области видимости, в которой определён a, она эту память просто заберёт себе. Т. е. a умрёт без принудительных вызовов delete, и утечек не произойдёт.

А если мы ручками открыли поток, то ручками должны его закрыть. GC за нас этого делать не должен (разве что создатель класса потока заставил его это делать).

Date: 2007-08-22 09:44 pm (UTC)
From: [identity profile] ximaera.livejournal.com
Ну, в таком случае да, полагаются.

Агрессивность сборщика мусора можно настраивать, если об этом речь.

Date: 2007-08-22 09:56 am (UTC)
From: [identity profile] demetr.livejournal.com
так получше, но всё равно С++ не С

Date: 2007-08-22 09:59 am (UTC)
From: [identity profile] esyr.livejournal.com
А какая разница, один фиг, GC на уровне языка не предусматривается. Так что за памятью и там, и там надо ручками следить.

Date: 2007-08-22 12:15 pm (UTC)
From: [identity profile] demetr.livejournal.com
в этом и их преимущество

Date: 2007-08-22 12:26 pm (UTC)
From: [identity profile] esyr.livejournal.com
Скажем так, я здесь этот вопрос обсуждать не хочу. Ибо у языков с GC тоже есть преимущества перед языками без оных.

Profile

esyr: (Default)
esyr

October 2010

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930
31      

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Aug. 4th, 2025 11:17 pm
Powered by Dreamwidth Studios