Параметризовать конструктор

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

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

Re: Параметризовать конструктор

Сообщение zub » 16.08.2014 01:13:12

stanilar
Примеров как реализовать подобное можно напридумывать много... с объектами, классами, рекордами, указателями, процедурными типами... хоть с кучей ифов или ветвистым case`ом.
Но это все не про то - задача создать объект тип которого станет известен в рантайме и вызов соответствующего конструктора вполне рабочее и логичное решение.

класс внутри обжекта (тогда уже сразу делать просто класс) или обжект с указателем или процедурным типом внутри - мы теряем все плюсы обжекта, всё что надо в нем уже есть, а мы городим еще один слой "виртуализации"
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение stanilar » 16.08.2014 10:56:33

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

Ифы и case'ы сложнее разделять по юнитам, код становится более запутанный. Но, как не практикующий сторонник ФП, буду солидарен с Вами, надо было просто определить TAnimal = (taDog, taCat) и налепить ифов с кейсами. Сопровождаемость и читаемость кода повысилась бы если не на порядки, то в разы уж точно.
stanilar
постоялец
 
Сообщения: 289
Зарегистрирован: 09.03.2010 19:09:02

Re: Параметризовать конструктор

Сообщение zub » 16.08.2014 12:27:40

>>Никаких плюсов обжектов не теряется, именно что "городим еще один слой "виртуализации".
Главный плюс - минимализм достаточный для данной задачи (PVMT всё что нужно чтоб организовать различное поведение экземпляров), его и теряем в первую очередь, т.к. придумываем свои нахлабучки. О возможности быстрого создания объестов без выделений памяти промолчу.

>>Сопровождаемость и читаемость кода повысилась бы если не на порядки, то в разы уж точно.
Шутите? Тогда хоть смайлы ставте. А если не шутите предлагаю представить читаемость и поддерживаемость LCL написаной в стиле "TControl = (tForm, tButton, tScrollBar, etc...) и налепить ифов с кейсами"
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение Дож » 16.08.2014 13:55:00

О возможности быстрого создания объестов без выделений памяти промолчу.

Вы не поняли, там нет никаких выделений памяти (там не экземпляр класса создаётся, а классовый тип записывается).
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение zub » 16.08.2014 14:56:21

Я понял и "промолчал" об этом)) имея ввиду вообще замену обжекта на класс.
В топике поставлена конкретная задача и найдено вполне нормальное решение, но пошли длинные рассуждалки на тему лучше делать вообще не так
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение stanilar » 16.08.2014 16:01:49

zub писал(а):Шутите?

Мне показалось любопытным Ваше замечание про ифы, в нем была большая доля истины.

Насчет LCL - а что в ней предлагается заменить на ифы? Там вроде особо и нечего менять.
stanilar
постоялец
 
Сообщения: 289
Зарегистрирован: 09.03.2010 19:09:02

Re: Параметризовать конструктор

Сообщение Дож » 31.08.2014 19:51:46

Прошу прощения у почтенной публики, в своём посте я написал чушь из-за тотального непонимания того, как работает VMT в объектах. Код перестанет работать, если в TAnimal добавить какие-то дополнительные поля.

Сейчас в Free Pascal у объектов ссылка на VMT размещается после всех полей. (В том случае, когда виртуальных методов нет, ссылки на VMT тоже не будет.) Не знаю зачем так сделано, возможно, для большей совместимости с обычными записями (и для совместимости в тех случаях, когда объект без виртуальных методов переписывается на объект с виртуальными методами). Поэтому, предполагаю, что чтобы корректно изменить VMT объекта, нужно (1) знать тот тип его предка, в котором VMT впервые появляется (2) используя какую-то магию с SizeOf(…) поменять ссылку.

Таким образом, я предлагаю такой рабочий код для изменения VMT вместо того, что в указанном посте:
Код: Выделить всё
[doj@korica ~/temp]$ cat switch.pas
type
TFlexible = object
  destructor Done; virtual;
  {function GetVMT: Pointer; inline;}
  procedure SetVMT(_VMT: Pointer); inline;
  property VMT: Pointer {read GetVMT} write SetVMT;
end;

TAnimal = object(TFlexible)
  FName: AnsiString;
  constructor Init(const Name: AnsiString);
  procedure TellAboutYourSelf; virtual; abstract;
end;

TCat = object(TAnimal)
  procedure TellAboutYourSelf; virtual;
end;

TDog = object(TAnimal)
  procedure TellAboutYourSelf; virtual;
end;

destructor TFlexible.Done;
begin
end;

procedure TFlexible.SetVMT(_VMT: Pointer);
begin
  PPointer(@Self)^ := _VMT;
end;

constructor TAnimal.Init(const Name: AnsiString);
begin
  FName := Name;
end;

procedure TCat.TellAboutYourSelf;
begin
  Writeln('Я кот по имени ', FName, '.');
end;

procedure TDog.TellAboutYourSelf;
begin
  Writeln('Я ', FName, ', гав-гав!');
end;

var
  Pet: TAnimal;

begin
  Pet.Init('Бусик');
  Pet.VMT := TypeOf(TCat);
  Pet.TellAboutYourSelf;
  Pet.VMT := TypeOf(TDog);
  Pet.TellAboutYourSelf;
  Pet.Done;
end.
[doj@korica ~/temp]$ fpc switch.pas && ./switch
Я кот по имени Бусик.
Я Бусик, гав-гав!
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение Sergei I. Gorelkin » 31.08.2014 20:58:46

VMT размещается не после всех полей, а в том месте, где встречается первый виртуальный метод. Если у потомков есть дополнительные поля - то VMT окажется где-то в середине. Если виртуальный метод объявить до полей в первом объекте иерархии - VMT будет в начале, как и у классов.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Параметризовать конструктор

Сообщение zub » 31.08.2014 21:01:47

Сейчас в Free Pascal у объектов ссылка на VMT размещается после всех полей. (В том случае, когда виртуальных методов нет, ссылки на VMT тоже не будет.) Не знаю зачем так сделано, возможно, для большей совместимости с обычными записями (и для совместимости в тех случаях, когда объект без виртуальных методов переписывается на объект с виртуальными методами). Поэтому, предполагаю, что чтобы корректно изменить VMT объекта, нужно (1) знать тот тип его предка, в котором VMT впервые появляется (2) используя какую-то магию с SizeOf(…) поменять ссылку.

Я когдато для этих целей сделал "парсер" исходников (их части с определениями объектов) чтоб обнаружить момент появления VMT и зафиксировать последовательность объявления виртуальных методов для возможности их самомтоятельного (чтото вроде выполнения в рантайме пользовательского скрипта) вызова в рантайме. Этот код практически неизменно работает со времен 2.0 до сегодняшнего 2.7.1. Единственное, когда я поддерживал совместимость с делфи, кажется в 2009 версии, в дельфях изменилось поведение - указатель на VMT стал появлятся в объектах независимо от присутствия виртуальных методов и для совместимости в мой базовый родительский объект пришлость добавить виртуальность. На делфи я давно забил, но VMT так в базовом объекте и болтается
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение MylnikovDm » 31.08.2014 21:37:03

Мужики, вы мазохисты!
MylnikovDm
постоялец
 
Сообщения: 103
Зарегистрирован: 15.02.2007 21:26:10
Откуда: Челябинск

Re: Параметризовать конструктор

Сообщение Дож » 31.08.2014 21:57:50

Sergei I. Gorelkin писал(а):VMT размещается не после всех полей, а в том месте, где встречается первый виртуальный метод. Если у потомков есть дополнительные поля - то VMT окажется где-то в середине.

Да, я не имел в виду, что VMT будет глобально после всех полей, я имел в виду, что будет после всех полей в рамках текущего объекта.

Sergei I. Gorelkin писал(а):Если виртуальный метод объявить до полей в первом объекте иерархии - VMT будет в начале, как и у классов.

А вот это не подтверждается на практике! По моим наблюдениям, даже если объявить виртуальный метод до полей, VMT всё равно будет в конце объекта. Иллюстрирую кодом:
Код: Выделить всё
[doj@korica ~/temp]$ cat vmt.pas
type
// Это базовый объект, в нём нет VMT вообще
TBaseObject = object
  Q, W: Pointer;
end;

// Это тестовый объект, в нём VMT будет после всех полей, а не между A и B
TTestObject = object(TBaseObject)
public
  A: Pointer;
  constructor Init;
  procedure Test; virtual;
public
  B: Pointer;
end;

// Этот объект унаследует положение VMT от предка
TDescendObject = object(TTestObject)
public
  X: Pointer;
  procedure Test1; virtual;
public
  Y: Pointer;
  procedure Test2; virtual;
end;

constructor TTestObject.Init;
begin
end;

procedure TTestObject.Test;
begin
end;

procedure TDescendObject.Test1;
begin
end;

procedure TDescendObject.Test2;
begin
end;

procedure Detect(const Name: String; var O; S: Integer; VMT: Pointer);
var
  I: Integer;
  A: array[0..1] of Pointer absolute O;
begin
  for I := 0 to S div SizeOf(Pointer) do
    if A[I] = VMT then
      Writeln(Name, '''s VMT found at ', I);
end;

procedure PrintPos(const Name: String; var O; var F: Pointer);
begin
  Writeln(Name, ' pos: ', (PtrUint(@F) - PtrUint(@O)) div SizeOf(Pointer));
end;

procedure PrintFields(const Name: String; S: Integer);
begin
  Writeln(Name, ' contains ', S div SizeOf(Pointer), ' fields');
end;

var
  T: TTestObject;
  D: TDescendObject;

begin
  PrintFields('TBaseObject', SizeOf(TBaseObject));

  T.Init;
  PrintFields('TTestObject', SizeOf(T));
  PrintPos('T.Q', T, T.Q);
  PrintPos('T.W', T, T.W);
  PrintPos('T.A', T, T.A);
  PrintPos('T.B', T, T.B);
  Detect('TTestObject', T, SizeOf(TTestObject), TypeOf(TTestObject));

  D.Init;
  PrintFields('TDescendObject', SizeOf(D));
  PrintPos('D.Q', D, D.Q);
  PrintPos('D.W', D, D.W);
  PrintPos('D.A', D, D.A);
  PrintPos('D.B', D, D.B);
  PrintPos('D.X', D, D.X);
  PrintPos('D.Y', D, D.Y);
  Detect('TDescendObject', D, SizeOf(TDescendObject), TypeOf(TDescendObject));
end.
[doj@korica ~/temp]$ fpc -Mobjfpc vmt.pas && ./vmt
TBaseObject contains 2 fields
TTestObject contains 5 fields
T.Q pos: 0
T.W pos: 1
T.A pos: 2
T.B pos: 3
TTestObject's VMT found at 4
TDescendObject contains 7 fields
D.Q pos: 0
D.W pos: 1
D.A pos: 2
D.B pos: 3
D.X pos: 5
D.Y pos: 6
TDescendObject's VMT found at 4
Последний раз редактировалось Дож 31.08.2014 22:06:54, всего редактировалось 1 раз.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение zub » 31.08.2014 22:02:36

>>Мужики, вы мазохисты!
Похоже на то)) В моем случае целью был скриптовый язык и инспектор объектов умеющий показывать-редактировать обычные рекорды и обжекты. Скрипт так у меня и неполучился, а инспектор работает. В итоге парсенье выборки исходников гораздо меньший мазохизм чем ручная регистрация всех типов показываемых в инспекторе. Возможно сейчас это можно сделать на основе RTTI, но во времена когда это писалось ртти мне нехватало, непомню подробностей. Еще думал на основе этого сделать автоматическую сериализацию, но так и не взялся, т.к. сохранять данные приходится в сторонние форматы.
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Параметризовать конструктор

Сообщение Дож » 01.09.2014 20:44:38

runewalsh писал(а):И, чтобы не создавать бесполезную тему, спрошу заодно здесь. Есть ли способ заставить исключения, брошенные из конструкторов object'ов, освобождать только что выделенную память? new(PObj, Init) => вызов Fail внутри Init немедленно dispose'ает объект и возвращает nil, но если взамен бросить исключение, происходит утечка.

Я смотрел исходники: оно освобождает объект, если было брошено исключение из конструктора (см. мои комментарии):
Код: Выделить всё
{$ifndef FPC_SYSTEM_HAS_FPC_HELP_CONSTRUCTOR}
// Дож: тут говорится, что _vmt:=-1 нужно, чтобы пометить выделенный в куче объект для fpc_help_fail
{ Note: _vmt will be reset to -1 when memory is allocated,
  this is needed for fpc_help_fail }
function fpc_help_constructor(_self:pointer;var _vmt:pointer;_vmt_pos:cardinal):pointer;[public,alias:'FPC_HELP_CONSTRUCTOR'];compilerproc;
var
  vmtcopy : pobjectvmt;
begin
  vmtcopy:=pobjectvmt(_vmt);
  { Inherited call? }
  if vmtcopy=nil then
    begin
      fpc_help_constructor:=_self;
      exit;
    end;

  if (_self=nil) and
     (vmtcopy^.size>0) then
    begin
      getmem(_self,vmtcopy^.size);
      { reset vmt needed for fail }
      _vmt:=pointer(-1); // Дож: помечаем
    end;
  if _self<>nil then
    begin
      fillchar(_self^,vmtcopy^.size,0);
      ppointer(_self+_vmt_pos)^:=vmtcopy;
    end;
  fpc_help_constructor:=_self;
end;
{$endif FPC_SYSTEM_HAS_FPC_HELP_CONSTRUCTOR}

...

{$ifndef FPC_SYSTEM_HAS_FPC_HELP_FAIL}
{ Note: _self will not be reset, the compiler has to generate the reset }
procedure fpc_help_fail(_self:pointer;var _vmt:pointer;vmt_pos:cardinal);[public,alias:'FPC_HELP_FAIL'];compilerproc;
begin
   if (_self=nil) or (_vmt=nil) then
     exit;
   { vmt=$ffffffff when memory was allocated }
   if ptruint(_vmt)=high(ptruint) then // Дож: тут мы обнаруживаем пометку
     begin
       if (_self=nil) or (ppointer(_self+vmt_pos)^=nil) then
         HandleError(210)
       else
         begin
           ppointer(_self+vmt_pos)^:=nil;
           freemem(_self); // Дож: освобождаем память!
           { reset _vmt to nil so it will not be freed a
             second time }
           _vmt:=nil;
         end;
     end
   else
     ppointer(_self+vmt_pos)^:=nil;
end;
{$endif FPC_SYSTEM_HAS_FPC_HELP_FAIL}


Добавлено спустя 3 часа 3 минуты 51 секунду:
В качестве резюме полученных знаний, я написал пост себе в блог:
http://keyfighter.blogspot.ru/2014/09/free-pascal.html
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Параметризовать конструктор

Сообщение Vapaamies » 02.09.2014 20:41:23

Прочитал статью, решил высказать свое мнение о FPC_CallPointerConstructor_Implemented. Эта процедура похожа на заточку (точку входа RTL) компилятора, вызов которой формируется кодогенератором. Нужно смотреть map-файл сборки. При отсутствии упоминания FPC_CallPointerConstructor_Implemented ее можно считать будущей или прошлой заточкой, еще/уже не используемой.

Это лишь предположение. В исходники FPC лезть лень. У меня снова приступ лени. :roll:
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Параметризовать конструктор

Сообщение Sergei I. Gorelkin » 02.09.2014 23:37:38

В модуле objects вообще хватает ужас-ужас-ужаса. Он изначально был завязан на детали вроде структуры кадра стека, характерные для i386. Поэтому работоспособности на MIPS, у которого frame pointer указывает на другой конец кадра (а чаще всего вообще не нужен), добивались даже не костылями...
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Пред.

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

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

Сейчас этот форум просматривают: Yandex [Bot] и гости: 34

Рейтинг@Mail.ru