Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Delphi программирование > Lazarus, Free Pascal, CodeTyphon
Регистрация

Восстановить пароль

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 04.04.2025, 12:57   #1
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию Автоматическое уничтожение объектов

Сколько видел программ на Free Pascal'е - везде объекты уничтожаются вручную, через Free или FreeAndNil. А ведь можно сделать чтобы это происходило автоматически. Для этого надо с объектами работать через интерфейсы и классы делать производными от TInterfacedObject. Переменная типа interface является интеллектуальным указателем, который контролирует использование объекта. Если пытаться использовать переменную типа interface без инициализации, то компилятор выдаст предупреждение. Когда уже никто не использует объект, переменная-интерфейс уничтожит его автоматически.

Вот пример: создадим объект очередь (queue) с двумя методами - Put (положить в очередь) и Get (взять из очереди). Создадим для этого объекта интерфейс QueueObj . Новая очередь будет создаваться в функции New:

Код:
unit queue;

{$mode ObjFPC}

interface
uses
  Classes, SysUtils;

type
  QueueObj = interface
    procedure Put( value: longint );
    procedure Get( var has_some: boolean; var value: longint);
  end;

function New : QueueObj;


implementation
type
  QueueImpl = class ( TInterfacedObject, QueueObj )
    public
      procedure Put( value: longint );
      procedure Get( var has_some: boolean; var value: longint);
      destructor Destroy; override;
  end;

destructor QueueImpl.Destroy;
begin
  writeln( 'queue destructor called.' );
end;

procedure QueueImpl.Put( value: longint );
begin
  writeln( 'Put called.' );
end;

procedure QueueImpl.Get( var has_some: boolean; var value: longint);
begin
  writeln( 'Get called.' );
  has_some := true;
  value := 5;
end;

function New : QueueObj;
begin
  Result := QueueImpl.Create;
end;

end.
Вот как пользоваться нашей очередью:

Код:
program project1;

uses queue;

var
  q: queue.QueueObj;

procedure Proc1( qu: queue.QueueObj );
var
  has: boolean;
  value: longint;
begin
  qu.Get( has, value );
  if has
  then begin
    writeln( value );
  end;
end;

begin
  q := queue.New;
  q.Put( 7 );
  Proc1( q );
end.
Вывод у программы:
$ ./project1
Put called.
Get called.
5
queue destructor called.

-------
Объект уничтожается автоматически. Деструктор вызывается.
DeepFlake вне форума Ответить с цитированием
Старый 05.04.2025, 09:55   #2
Valick
Форумчанин
 
Регистрация: 27.04.2022
Сообщений: 519
По умолчанию

Положили 7, а достали 5, любая бухгалтерия позваидует
Pascal последний раз мимопроходил в 93 году. В какой момент вызывается деструктор? Когда программа встречает директиву end?
Valick вне форума Ответить с цитированием
Старый 05.04.2025, 15:10   #3
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию

Цитата:
Сообщение от Valick Посмотреть сообщение
Положили 7, а достали 5, любая бухгалтерия позваидует
Это не баг, это фича . Кстати, язык 1С:Предприятие паскалеподобный, только по-русски.

Цитата:
Сообщение от Valick Посмотреть сообщение
В какой момент вызывается деструктор? Когда программа встречает директиву end?
Нет. Переменная типа "interface" является фактически разделяемым интеллектуальным указателем, то есть хранит в себе количество ссылок на объект. Счётчик увеличивается когда эта переменная присваивается другой переменной, и уменьшается, когда поток исполнения программы выходит из области объявления этой второй переменной. Когда счётчик становится нулём, то происходит уничтожение объекта, и вызов деструктора.
В данной программе счётчик q перед вызовом Proc1( q ) равен 1, внутри процедуры Proc1 счётчик увеличивается, так как происходит неявное присваивание параметру процедуры qu := q , поэтому он там 2.
После исполнения Proc1 счётчик q опять уменьшился в 1, потому что qu уже нет. Так как q определён как глобальная переменная, то при завершении программы он исчезает из области программы и счётчик уменьшается в 0. Тогда и уничтожается объект.

Вот можно почитать про Free Pascal https://castle-engine.io/modern_pascal
DeepFlake вне форума Ответить с цитированием
Старый 05.04.2025, 17:58   #4
Valick
Форумчанин
 
Регистрация: 27.04.2022
Сообщений: 519
По умолчанию

Я не правильно выразился. Я имел ввиду именно ваш код. В какой момент именно в вашем коде.
В PHP поведение деструкторов похожее, хотя в силу специфики языка там трудно найти им применение.

Последний раз редактировалось Valick; 05.04.2025 в 18:01.
Valick вне форума Ответить с цитированием
Старый 05.04.2025, 18:54   #5
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию

Конкретно в этой программе - в самом конце блока операторов программы, перед end.

Деструктор вызывается при уничтожении объекта. А объект уничтожается когда количество ссылок на него 0.
То есть если допустим, переменная-интерфейс инициализируется в какой-то функции, и по завершении этой функции больше никто не ссылается на эту переменную, то как функция завершится то уничтожится объект, связанный с переменной-интерфейсом.
DeepFlake вне форума Ответить с цитированием
Старый 05.04.2025, 20:01   #6
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию

Интеллектуальные указатели это хорошо, но ведь они могут зациклиться, и тогда память не освободится. Значит надо использовать анализаторы памяти для нахождения утечек памяти. Другое дело что утечки могут быть и не в вашем коде, но всё же. Во Free Pascal'е есть встроенный анализатор памяти - HeapTrc, также можно использовать внешний - valgrind.
Чтобы включить HeapTrc, надо включить опции компиляции -gl -gh . В Lazarus IDE надо открыть Project Options, там Compiler Options -> Debugging и там установить флажки для -gl и -gh .

Вот программа, на которой будем проверять работу анализатора памяти. В ней два объекта x1 и x2, с которыми работаем через интерфейсы (то есть они должны автоматически уничтожатся когда не используются).
В x1 (объект T1) есть ссылка на объект T2, а в x2 (объект T2) есть ссылка на объект T1.

В процедуре Test тестируем без зацикливания. x1 ссылается (во внутреннем поле) на x2, а x2 ни на что не ссылается. То есть зацикливания нет и оба объекта должны освободиться при завершении программы.

В процедуре Test2 делаем зацикливание. x1 ссылается на x2, а x2 ссылается на x1.

Вот текст программы:
Код:
program project1;

type
  T2Obj = interface;
  T1Obj = interface
    procedure Set2( a_t2_obj: T2Obj );
  end;

  T2Obj = interface
    procedure Set1( a_t1_obj: T1Obj );
  end;

  T1Impl = class ( TInterfacedObject, T1Obj )
    public
      procedure Set2( a_t2_obj: T2Obj );
    private
      t2: T2Obj;
  end;

  T2Impl = class ( TInterfacedObject, T2Obj )
    public
      procedure Set1( a_t1_obj: T1Obj );
    private
      t1: T1Obj;
  end;

procedure T1Impl.Set2( a_t2_obj: T2Obj );
begin
  t2 := a_t2_obj;
end;

procedure T2Impl.Set1( a_t1_obj: T1Obj );
begin
  t1 := a_t1_obj;
end;

function NewT1 : T1Obj;
begin
  Result := T1Impl.Create;
end;

function NewT2 : T2Obj;
begin
  Result := T2Impl.Create;
end;

procedure Test;
var
  x1: T1Obj;
  x2: T2Obj;
begin
  x1 := NewT1;
  x2 := NewT2;
  x1.Set2( x2 );
end;

procedure Test2;
var
  x1: T1Obj;
  x2: T2Obj;
begin
  x1 := NewT1;
  x2 := NewT2;
  x1.Set2( x2 );
  x2.Set1( x1 );
end;

begin
//  Test;
  Test2;
end.
Сначала запускаем Test. Вот вывод программы:

$ ./project1
Heap dump by heaptrc unit of /home/ville/mydevelop/laz/mleak/project1
2 memory blocks allocated : 80/80
2 memory blocks freed : 80/80
0 unfreed memory blocks : 0
True heap size : 32768
True free heap : 32768

то есть всё в порядке, память освободилась.

Вот результат работы Test2:

$ ./project1
Heap dump by heaptrc unit of /home/ville/mydevelop/laz/mleak/project1
2 memory blocks allocated : 80/80
0 memory blocks freed : 0/0
2 unfreed memory blocks : 80
True heap size : 32768
True free heap : 32256
Should be : 32304
Call trace for block $00007FF012D23200 size 40
$00000000004012D0 Test2, line 63 of project1.lpr
$00000000004013AE $main, line 70 of project1.lpr
Call trace for block $00007FF012D23100 size 40
$00000000004012C7 Test2, line 62 of project1.lpr
$00000000004013AE $main, line 70 of project1.lpr

Отловил два неосбождённых блока памяти. Утечка.
Изображения
Тип файла: jpg Screenshot_2025-04-05_20-33-08.jpg (102.3 Кб, 0 просмотров)
DeepFlake вне форума Ответить с цитированием
Старый 07.04.2025, 14:14   #7
phomm
personality
Старожил
 
Аватар для phomm
 
Регистрация: 28.04.2009
Сообщений: 2,899
По умолчанию

Цитата:
Сообщение от Valick Посмотреть сообщение
Положили 7, а достали 5, любая бухгалтерия позваидует
это потому что очередь совсем бессмысленно написана, не хранит, чего закинуто душнила_офф
По теме: не особо могу сказать чего, т.к. здесь более менее азбучные истины, и автор, полагаю, решил начать свой просветительский/изыскательский блог здесь.
Это похвально, конечно, и лично я не против, но, по сути, тема не ставит никакой цели/вопроса перед остальным сообществом. А если есть, то озвучьте, пожалуйста.
Приятно также наличие ссылки на годный труд от автора движка Касла, интересуетесь, кстати ?

Также могу по мелочи покритиковать/попридираться
Например, для метода Гет очереди стоит использовать out параметры (а булевый можно ещё и результатом функции передать, чтобы не писать if has )
Также конвенции бы соблюсти - I и T префиксы
Ещё недо-фабрика New: мало того что идентификатор зарезервирован (https://wiki.freepascal.org/Reserved_words) так ещё и смысла в ней мизер, т.к. она генерит только единственный тип, можно легко это и явным вызовом конструктора делать, и не надо определение класса скрывать в имплементации

Последний раз редактировалось phomm; 07.04.2025 в 14:26.
phomm вне форума Ответить с цитированием
Старый 08.04.2025, 13:50   #8
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию

phomm, спасибо за замечания, всегда есть что улучшить в коде. Да, я здесь вопрос не задаю, это для популяризации Паскаля. Люди ведь интересуются, вот недавно один человек спрашивал https://programmersforum.ru/showthread.php?t=347710

Я считаю, что Free Pascal - лучший язык для изучения основ программирования, ну это, императивное, структурное, процедурное, и т.д. , единственно, что на Free Pascal'е нельзя функциональное программирование, так это и не надо, для этого Lisp.

А конкретно про интерфейсы - я сразу же сказал, что заметил что народ не особо их использует, считает что это для работы с COM и CORBA, но это ведь не так.

Насчёт ваших замечаний по коду:
1. out - да, лучше так.
2. префиксы I и T - я это не люблю.
3. Get в виде функции - нет, это не нравится. Процедуры-функции введены в Pascal только лишь для удобства записи математических функций. Ещё можно использовать для функций-фабрик объектов. И всё. Дело в том, что программирование процедур-функций в Паскале ненадёжно (даже при использовании Result), поэтому их надо избегать.
4. Насчёт New: я конечно, сильно удивлён, почему парни написали там что New это зарезервированное ключевое слово. Это не так. New - это всего лишь процедура из модуля system. Когда в тексте написано New(p), то компилятор автоматически переводит это в system.New(p). Поэтому можно в любом модуле описывать свои процедуры New, и никаких коллизий не возникнет.
5. Насчёт "явным вызовом конструктора делать, и не надо определение класса скрывать в имплементации" - так ведь, извините меня, вся эта белиберда с интерфейсами и задумана чтобы скрывать реализацию объекта. Это называется инкапсуляция.

------
Вот корректная реализация очереди из первого сообщения. Там используется список из модуля IntegerList, а этот модуль находится в пакете LazUtils, то есть этот пакет надо подключить к зависимостям проекта. Для этого в Lazarus'е открыть Project Inspector, щёлнуть правой конпкой мыши по тексту Required Packages, выбрать Add, и в списке выбрать LazUtils.

Код:
unit queue;

{$mode ObjFPC}

interface
uses
  Classes, SysUtils, IntegerList;

type
  QueueObj = interface
    procedure Put( value: longint );
    procedure Get( out has_some: boolean; out value: longint);
  end;

function New : QueueObj;


implementation
type
  QueueImpl = class ( TInterfacedObject, QueueObj )
    public
      procedure Put( value: longint );
      procedure Get( out has_some: boolean; out value: longint);
      destructor Destroy; override;
      constructor Create;
    private
      data_list: TInt64List;
  end;

destructor QueueImpl.Destroy;
begin
  writeln( 'queue destructor called.' );
  FreeAndNil( data_list );
end;

constructor QueueImpl.Create;
begin
  writeln( 'queue constructor called.' );
  data_list := TInt64List.Create;
end;

procedure QueueImpl.Put( value: longint );
begin
  writeln( 'Put called. value=', value );
  data_list.Add( value );
end;

procedure QueueImpl.Get( out has_some: boolean; out value: longint);
begin
  writeln( 'Get called.' );
  has_some := false;
  if data_list.Count > 0
  then begin
    has_some := true;
    value := longint( data_list.First );
    data_list.Remove( data_list.First );
  end;
end;

function New : QueueObj;
begin
  Result := QueueImpl.Create;
end;

end.
Вот использование очереди:
Код:
program project1;

uses queue;

var
  q: queue.QueueObj;

procedure Fetch( const qu: queue.QueueObj );
var
  has: boolean;
  value: longint;
begin
  qu.Get( has, value );
  if has
  then begin
    system.WriteLn( value );
  end
  else begin
    system.WriteLn( 'no value' );
  end;
end;

begin
  q := queue.New;
  q.Put( 7 );
  q.Put( 7 );
  Fetch( q );

  q.Put( 34 );
  q.Put( -8 );
  Fetch( q );
  Fetch( q );
  Fetch( q );
  Fetch( q );

end.
Печатает
Код:
$ ./project1 
queue constructor called.
Put called. value=7
Put called. value=7
Get called.
7
Put called. value=34
Put called. value=-8
Get called.
7
Get called.
34
Get called.
-8
Get called.
no value
queue destructor called.
Изображения
Тип файла: jpg Screenshot_2025-04-08_13-34-32.jpg (97.1 Кб, 0 просмотров)
DeepFlake вне форума Ответить с цитированием
Старый 08.04.2025, 16:08   #9
phomm
personality
Старожил
 
Аватар для phomm
 
Регистрация: 28.04.2009
Сообщений: 2,899
По умолчанию

Цитата:
Сообщение от DeepFlake Посмотреть сообщение
2. префиксы I и T - я это не люблю.
пу-пу-пу, ладно, интересное следование одним "правилам" и нелюбовь к другим

Цитата:
Сообщение от DeepFlake Посмотреть сообщение
3. Get в виде функции - нет, это не нравится. Процедуры-функции введены в Pascal только лишь для удобства записи математических функций. Ещё можно использовать для функций-фабрик объектов. И всё. Дело в том, что программирование процедур-функций в Паскале ненадёжно (даже при использовании Result), поэтому их надо избегать.
вот , например, вы где-то узнали, что функций надо избегать и приняли это за правило, при том, что стандартные библиотеки преспокойно используют и функции и сущность Result, наверное, их разработчики не любят это правило. Критерии ненадёжности бы.. и, желательно, не такие, где стреляют в ногу.

Цитата:
Сообщение от DeepFlake Посмотреть сообщение
4. Насчёт New: я конечно, сильно удивлён, почему парни написали там что New это зарезервированное ключевое слово. Это не так. New - это всего лишь процедура из модуля system. Когда в тексте написано New(p), то компилятор автоматически переводит это в system.New(p). Поэтому можно в любом модуле описывать свои процедуры New, и никаких коллизий не возникнет.
тут я не спорю, что это специализированное слово и его можно использовать для своих целей в том числе, а в списке зарезервированных оно как рекомендация, но банально читатель вашего кода будет недоумевать.

Цитата:
Сообщение от DeepFlake Посмотреть сообщение
5. Насчёт "явным вызовом конструктора делать, и не надо определение класса скрывать в имплементации" - так ведь, извините меня, вся эта белиберда с интерфейсами и задумана чтобы скрывать реализацию объекта. Это называется инкапсуляция.
про сокрытие и интерфейсы я не спорю, вопрос только к функции, ибо она ничего не делает, т.к.
про явный вызов имелось в виду что мы в интерфейсную переменную можем положить любую реализацию, например , из другого модуля, а функция только множит сущности без надобности, поэтому я назвал её недофабрикой
Вот если бы был чисто интефейс в модуле, и его подключал потребитель, а также модуль где была бы нормальная фабрика, которая, например, имела выбор между несколькими реализациями, через IntegerList из пакета либо через Generics.Collections.List<Integer> то из всего этого появляется смысл. а так- скорытие ради сокрытия, лишние сущности.

Цитата:
Сообщение от DeepFlake Посмотреть сообщение
заметил что народ не особо их использует
не стоит агрументировать, ссылаясь на народ, пропагандистский прием. Люди делают как удобно и как знают, к тому же, подозреваю, что у вас выборка (для громкого слова народ) не такая уж и большая, надеюсь, вы не только этот форум рассматриваете, посмотрите какие-нибудь серьёзные open-source проекты на паскале, например https://github.com/exilon/QuickLib/b.../Quick.IOC.pas там вполне активно используются интерфейсы, где нужно.
phomm вне форума Ответить с цитированием
Старый 10.04.2025, 14:11   #10
DeepFlake
Форумчанин
 
Регистрация: 16.05.2024
Сообщений: 200
По умолчанию

Форум хорош для высказывания своего мнения, можно узнать что-то новое, никто не может всё знать. Но только когда обсуждение ведётся в вежливой и конструктивной форме и не переходит на личностные нападки.
Я напишу кое-какие свои соображения, но это только для тех, кто мыслит конструктивно.

Есть такая практика: кодирование в имени переменной тип этой переменной. Хорошо это или плохо?
Это ведь появилось не от хорошей жизни. В низкоуровневых языках и сценарных языках нет типизации или она слабая или динамическая, поэтому, глядя на текст программы, совершенно непонятно что в этой переменной. И кодирование типа в имени позволяет программисту понять что там и избежать ошибок. Например,
x_i = y2_b;
сразу бросается в глаза несоответствие, что мы целой переменной пытаемся присвоить логическое значение, а это ошибка.
Однако в языках со строгой статической типизацией это кодирование излишне и только ухудшает ясность.

Точно так же идея кодирования типа в названии типа (путем префикса T и I ) появилась не от хорошей жизни. Это пришло из C++, где нет отдельного типа для интерфейсов, а интерфейсы описываются при помощи классов. То есть объекты описываются классами, и интерфейсы тоже классами. И глядя на текст программы трудно понять с чем мы имеем дело - с объектом или интерфейсом. Поэтому и стали добавлять префикс T в название типа объекта и I - для интерфейсов.
Но во Free Pascale для интерфейсов есть отдельный тип - interface, и он совершенно другой чем class. Невозможно синтаксически перепутать объект и интерфейс, так как у интерфейса нет конструкторов, деструкторов и полей.
Поэтому использование префиксов T и I во Free Pascale я считаю излишним и ухудшающим чтение.

Придираться к моей реализации очереди не смысла, так как цель была показать не как надо программировать очередь, а интерфесы, как определить и использовать. Тип-интерфейс как контракт по использованию и переменная-интерфейс как интеллектуальный указатель.

Про функции:
компилятор Free Pascal и Lazarus, действительно, в своём коде широко используют процедуры-функции, однако кто сказал что это правильно? Если посмотреть в их код - то это вообще, пример как не надо программировать. Во Free Pascale существуют все средства для надёжного программирования, это безопасные указатели (переменные типа class), интерфейсы (контракты) и автоматическое управление памятью (переменные типа interface). Однако в коде Free Pascalя и Lazarusа и их фремворках повсеместно используются небезопасные указатели (pointer и ^T), функции, вычисления по короткой схеме, а интерфейсы вообще не используются. Их код - это не то, на что людям следует ориентироваться.

Ненадёжность программирования функций во Паскале признают все, даже на wiki free Pascalя сказано что легко случайно сделать бесконечную рекурсию. И Никлаус Вирт тоже признал. В своих последующих языках после Паскаля - Modula-2 и Oberon - он изменил синтаксис функций, они стали возврат значения делать через return.

Всех поздравляю с серединой весны!
DeepFlake вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Уничтожение объектов atenon C++ Builder 3 01.06.2012 18:44
Создание и уничтожение объектов. Время жизни объектов C++/C# Anett// Помощь студентам 0 24.10.2011 23:26
Автоматическое уничтожение объекта mutabor Общие вопросы Delphi 6 25.06.2008 22:25
Создание/уничтожение объектов nimf Общие вопросы Delphi 10 14.04.2008 10:54