Странный пример кода из документации

Форум для изучающих FPC и их учителей.

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

Странный пример кода из документации

Сообщение Dmitro25 » 29.11.2019 20:49:33

Здравствуйте!
Я новичок во FreePascal, но не новичок в Delphi.
В процессе изучения документации "Free Pascal Reference guide" наткнулся на пример кода, который показался мне странным (CHAPTER 5. OBJECTS - Class or static methods, стр. 79 PDF-документа):
Код: Выделить всё
{$APPTYPE CONSOLE}
{$IFDEF FPC}{$MODE DELPHI}{$H+}{$ENDIF}
type
  TTest = object
  const
    Epsylon = 100;
  var
    f: integer;
    class var cv1, cv2: integer;
    procedure myproc;
    class procedure testproc;
    class procedure testproc2; static;
    procedure testproc3; static;
  end;

  procedure TTest.myproc;
  begin
    cv1 := 0;
    f := 1;
  end;

  class procedure TTest.Testproc;
  begin
    cv1 := 1;
    // f:=1;
  end;

  class procedure TTest.Testproc2;
  begin
    cv1 := 2;
    // f:=1;
  end;

  procedure TTest.Testproc3;
  begin
    cv1 := 3;
    // f:=1;
  end;

var
  P: procedure;
begin
  P := @TTest.Myproc;
  P := @TTest.Testproc;
  P := @TTest.Testproc2;
  P := @TTest.Testproc3;
end. 

Программа действительно компилируется. Но, по-моему, не должна работать правильно.
Больше всего меня смущает строчка: "P := @TTest.Myproc;" - ведь на момент данного присваивания объект типа TTest ещё не создан, так что если мы выполним после данной строчки что-то типа "P();", то непонятно, с какими данными будет работать вызванная функция "myproc" - ведь переменная "f" не размещена в памяти?
И второй момент: непонятно, почему переменная "P" объявлена как "procedure", а не "procedure of object"? Насколько я помню, в Delphi (а программа написана с {$MODE DELPHI}) для доступа к методам объекта, даже если они статические, всё равно эта переменная должна быть "procedure of object".

И ещё один вопрос: я попытался скомпилировать данную программу (закомментировав строчку "P := @TTest.Myproc;") в режиме {$MODE objfpc}, но мне не удалось, как бы я ни объявлял переменную "P" ("procedure;" или "procedure of object;"), также я пытался ставить и убирать знак "@" в выражении "P := @TTest.Testproc;" - в любом случае компилятор выдаёт мне сообщение об ошибке несоответствия типов в данной строке. Как в данном режиме сделать правильно присвоение переменной процедурного типа?
Dmitro25
незнакомец
 
Сообщения: 7
Зарегистрирован: 29.11.2019 20:02:46

Re: Странный пример кода из документации

Сообщение Vadim » 30.11.2019 04:53:37

Dmitro25
Примерный перевод из этого места "Free Pascal Reference guide":
Методы класса или методы, объявленные с помощью статической директивы, являются методами, которые являются глобальными для типа объекта. При вызове неявный указатель «self» недоступен. Это означает, что обычные методы не могут быть вызваны, и ни одно из полей объекта не может быть доступно. Однако можно использовать переменные класса.
Классовые или статические методы являются обычными методами, их можно назначить процедурной переменной.
Следующая программа демонстрирует все это. Закомментированные позиции не будут компилироваться.

Т.е. несмотря на то, что объект не создан, сами методы\процедуры, если они объявлены именно так, как раз существуют и ими можно воспользоваться как обычными процедурами, только нужно получить их адрес с помощью "@". Так же явно указано, что полями класса пользоваться нельзя, а вот переменными класса - можно. Таким образом у нас существует процедура и существует "var f", которой можно пользоваться в просто процедуре, но нельзя в классной процедуре. Никлаус Вирт это бы не одобрил, но его мнение уже давно никто не спрашивает... :D
Dmitro25 писал(а):непонятно, почему переменная "P" объявлена как "procedure", а не "procedure of object"?

Видимо как раз в связи с вышеперечисленным. :D Т.е. как процедурой ею пользоваться можно, а как процедурой объекта - нельзя. Но лучше об этом полюбопытствовать у разработчиков.
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Странный пример кода из документации

Сообщение Дож » 30.11.2019 06:20:27

Пример действительно очень странный, особенно странно его видеть в этом разделе документации.

1. Есть ощущение, что в DELPHI режиме компилятор вообще не проверяет типы методов. Если, например, добавить методу параметры или возвращаемое значение, или поменять тип P, то присвоение всё равно скомпилируется, как если бы @ просто возвращала нетипизированный указатель на метод, и дальше приводила бы его к типу переменной. (Отсюда возникает вопрос про корректность не только P:=@TTest.Myproc, но и остальных трёх строчек.)

2. Похоже, что получение статичных методов не поддерживается https://lists.freepascal.org/pipermail/ ... 26250.html (но это инфа 10летней давности, с тех пор что-то могло поменяться).

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

Re: Странный пример кода из документации

Сообщение iskander » 30.11.2019 09:15:20

А что говорит душка Delphi насчёт этого примера?
iskander
энтузиаст
 
Сообщения: 606
Зарегистрирован: 08.01.2012 18:43:34

Re: Странный пример кода из документации

Сообщение Dmitro25 » 30.11.2019 10:16:09

iskander писал(а):А что говорит душка Delphi насчёт этого примера?

Delphi (у меня 7-ой) компилирует только если объявить "P : Procedure of object;", закомментировать строку "P:=TTest.Myproc;" и убрать "@" в последующих строках (ну, не считая того, что пришлось закомментировать в определении самого объекта "const Epsylon", "class var" сделать просто глобальными переменными и "static" заменить на "class procedure" - но это несущественно для данного примера).
И это поведение вполне (на мой взгляд) корректно. В Delphi, насколько я помню, статические методы класса вызываются так же, как и нестатические (т.е. в них тоже передаётся скрытый параметр Self, но только равный nil), поэтому указатели на такие методы - они тоже "of object". С одной стороны это чуть менее эффективно, т.к. при модели вызова "register" теряется лишний регистр, а с другой - достигается большая универсальность (например, если какому-то компоненту для работы требуется callback-функция типа "of object", ему можно передать и статический, и нестатический метод объекта).
А создатели FreePascal, видимо, решили статические методы оформить просто как глобальные функции, но почему только для режима "delphi" и почему при этом не проверяются сигнатуры методов - непонятно.
Dmitro25
незнакомец
 
Сообщения: 7
Зарегистрирован: 29.11.2019 20:02:46

Re: Странный пример кода из документации

Сообщение iskander » 30.11.2019 10:59:25

Delphi 10.3 CE этот пример компилирует вот в таком виде(ей непонятен модификатор static у неклассового метода):
Код: Выделить всё
program test;

{$APPTYPE CONSOLE}
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}

type

  TTest = object
    const Epsylon = 100;
    var f : integer;
    class var cv1,cv2:integer;
    procedure myproc;
    class procedure testproc;
    class procedure testproc2; static;
    procedure testproc3; //static;
  end;

  procedure TTest.myproc;
  begin
    cv1:=0;
    f:=1;
  end;

  class procedure TTest.Testproc;
  begin
    cv1:=1;
    f:=1;
  end;

  class procedure TTest.Testproc2;
  begin
    cv1:=2;
    // f:=1;
  end;

  procedure TTest.Testproc3;
  begin
    cv1:=3;
    f:=1;
  end;

var
  P: procedure;

begin
  P:=@TTest.Myproc;
  P:=@TTest.Testproc;
  P:=@TTest.Testproc2;
  P:=@TTest.Testproc3;
end.

>>
Код: Выделить всё
Checking project dependencies...
Building test.dproj (Release, Win32)
[dcc32 Hint] test.dpr(49): H2077 Value assigned to 'P' never used
[dcc32 Hint] test.dpr(48): H2077 Value assigned to 'P' never used
[dcc32 Hint] test.dpr(47): H2077 Value assigned to 'P' never used
[dcc32 Hint] test.dpr(46): H2077 Value assigned to 'P' never used
Success
Elapsed time: 00:00:00.4


Добавлено спустя 55 минут 3 секунды:
Но вы правы, пример безусловно странный.
По второму вопросу, в режиме {$mode objfpc} код
Код: Выделить всё
  //P:=@TTest.Myproc;
  //P:=@TTest.Testproc;
  P:=@TTest.Testproc2;
  //P:=@TTest.Testproc3; 

FPC 3.3.1 вполне компилирует.
iskander
энтузиаст
 
Сообщения: 606
Зарегистрирован: 08.01.2012 18:43:34

Re: Странный пример кода из документации

Сообщение Dmitro25 » 30.11.2019 15:31:39

iskander
Слушайте, а как у вас Delphi допустил компиляцию вашего кода, если в классовой процедуре "class procedure TTest.Testproc;" присваивается "f:=1;", ведь f - переменная, относящаяся к экземпляру, а не к классу в целом?

FPC 3.3.1 вполне компилирует.

У меня версия 3.0.4 (вроде, последний официальный релиз) компилировать эту строку ("P:=@TTest.Testproc2;") в режиме "objfpc" отказывается: Error: Incompatible types: got "<class method type of procedure of object;Register>" expected "<procedure variable type of procedure;Register>"
Dmitro25
незнакомец
 
Сообщения: 7
Зарегистрирован: 29.11.2019 20:02:46

Re: Странный пример кода из документации

Сообщение Alex2013 » 30.11.2019 15:41:03

Вообще не понял смысла этого куска
Код: Выделить всё
  P := @TTest.Myproc;
  P := @TTest.Testproc;
  P := @TTest.Testproc2;
  P := @TTest.Testproc3;

Это же не вызов метода а просто тупое присваивание адреса .
Я бы понял если бы было что-то вроде P := @TTest.Myproc;P;
Кроме того не ясны причины использования object вместо class
(особенно странно что в описании методов при этом используют именно модификатор class ).
Alex2013
долгожитель
 
Сообщения: 3049
Зарегистрирован: 03.04.2013 11:59:44

Re: Странный пример кода из документации

Сообщение iskander » 30.11.2019 16:08:43

Dmitro25 писал(а):Слушайте, а как у вас Delphi допустил компиляцию вашего кода, если в классовой процедуре "class procedure TTest.Testproc;" присваивается "f:=1;", ведь f - переменная, относящаяся к экземпляру, а не к классу в целом?

Ага, заметили! Ну, полагаю, Alex2013 прав. Операция взятия адреса возвращает нетипизированный указатель, процедурный тип по сути просто типизированный указатель.
Синтаксис не запрещает присваивать нетипизированный указатель типизированному. Дальше dcc32 умывает руки.
Если мы хотим типизацию, нужно что-то вроде
Код: Выделить всё
  P:=TTest.Myproc;
  P:=TTest.Testproc;
  P:=TTest.Testproc2;
  P:=TTest.Testproc3;
iskander
энтузиаст
 
Сообщения: 606
Зарегистрирован: 08.01.2012 18:43:34

Re: Странный пример кода из документации

Сообщение Dmitro25 » 30.11.2019 21:35:58

iskander писал(а):Операция взятия адреса возвращает нетипизированный указатель

Да я даже не про операцию взятия адреса, а про тело самой процедуры "class procedure TTest.Testproc;", как она-то скомпилировалась, если в ней переменная "f" используется?
Dmitro25
незнакомец
 
Сообщения: 7
Зарегистрирован: 29.11.2019 20:02:46

Re: Странный пример кода из документации

Сообщение iskander » 30.11.2019 21:54:20

Dmitro25 писал(а):как она-то скомпилировалась, если в ней переменная "f" используется?

Скорее всего баг компилятора.
iskander
энтузиаст
 
Сообщения: 606
Зарегистрирован: 08.01.2012 18:43:34

Re: Странный пример кода из документации

Сообщение serbod » 01.12.2019 03:14:39

Это не class, а object. И переменная похожа на статическую (глобальную). Что-то из времён Турбопаскаля.
Аватара пользователя
serbod
постоялец
 
Сообщения: 449
Зарегистрирован: 16.09.2016 11:03:02
Откуда: Минск

Re: Странный пример кода из документации

Сообщение Vadim » 01.12.2019 06:19:41

лучше бы вообще избегать пользоваться в своём коде такими "фичами". Вносят путаницу в понимание кода. ;-)

Добавлено спустя 10 минут 38 секунд:
Alex2013 писал(а):Я бы понял если бы было что-то вроде P := @TTest.Myproc;P;

А Вы попробуйте придумать необходимость именно так вызывать эти методы ещё не созданного объекта. Вот и разработчики тоже не придумали... :D
Alex2013 писал(а):особенно странно что в описании методов при этом используют именно модификатор class

Как раз в данном случае модификатор "class" играет ключевую роль. Поскольку это метод класса, а не конкретного уже созданного объекта, в нём отсутствует параметр "self", что и делает метод обычной процедурой и ничем более. И, соответственно, позволяет присвоить его адрес процедурешной переменной.
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Странный пример кода из документации

Сообщение iskander » 01.12.2019 08:26:05

serbod писал(а):И переменная похожа на статическую (глобальную).

Интересно, каким местом похожа?
iskander
энтузиаст
 
Сообщения: 606
Зарегистрирован: 08.01.2012 18:43:34

Re: Странный пример кода из документации

Сообщение serbod » 02.12.2019 11:14:07

iskander писал(а):
serbod писал(а):И переменная похожа на статическую (глобальную).

Интересно, каким местом похожа?


Меня смутил "var" при объявлении поля f. Раньше не встречал такого. В документации турбопаскаля и Delphi 7 не нашел ничего на эту тему. В документации FreePascal есть упоминание, что var может стоять перед именем поля, но что она дает - не написано. А в исходниках компилятора лень копаться.

В примере из стартового сообщения по сути никакой код не выполняется, там только взятие адреса методов объекта. Как пример взятия адреса годится, но объявления переменных и тел методов только отвлекают.
Аватара пользователя
serbod
постоялец
 
Сообщения: 449
Зарегистрирован: 16.09.2016 11:03:02
Откуда: Минск

След.

Вернуться в Обучение Free Pascal

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

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 13

Рейтинг@Mail.ru